//
// Copyright (C) 2013-2018 Paolo Tosco, Greg Landrum
//
// @@ All Rights Reserved @@
// This file is part of the RDKit.
// The contents are covered by the terms of the BSD license
// which is included in the file license.txt, found at the root
// of the RDKit source tree.
//
#include "O3AAlignMolecules.h"
#include <RDGeneral/utils.h>
#include <Numerics/Vector.h>
#include <GraphMol/Substruct/SubstructMatch.h>
#include <GraphMol/Conformer.h>
#include <GraphMol/ROMol.h>
#include <GraphMol/MolOps.h>
#include <GraphMol/AtomIterators.h>
#include <Numerics/Alignment/AlignPoints.h>
#include <GraphMol/MolTransforms/MolTransforms.h>
#include <boost/dynamic_bitset.hpp>
#include <boost/algorithm/string.hpp>
#include <RDGeneral/RDThreads.h>

#ifdef RDK_BUILD_THREADSAFE_SSS
#include <thread>
#include <future>
#endif

double square(double x) { return x * x; }

namespace RDKit {
namespace MolAlign {
static std::uint8_t mmffSimMatrix[99][99] = {
    {1,  3,  4, 3, 0, 4, 6,  7,  5,  6,  7,  4,  5,  7,  3,  5,  6,  7,  3, 2,
     0,  2,  0, 0, 6, 7, 0,  0,  0,  3,  0,  10, 0,  10, 10, 0,  3,  6,  7, 6,
     8,  7,  8, 4, 7, 6, 8,  7,  10, 0,  10, 0,  6,  10, 12, 14, 12, 10, 4, 4,
     6,  10, 3, 3, 6, 6, 8,  8,  8,  8,  0,  10, 10, 4,  5,  10, 10, 3,  6, 10,
     12, 8,  0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10},
    {3,  1,  2, 2, 0, 5, 6, 7, 3,  5,  7,  4,  5, 7,  3,  4,  5,  6,  3, 2,
     0,  2,  0, 0, 5, 8, 0, 0, 0,  2,  0,  10, 0, 8,  10, 0,  2,  4,  5, 5,
     6,  4,  7, 4, 6, 5, 7, 6, 10, 0,  8,  0,  4, 10, 12, 14, 12, 10, 4, 3,
     5,  10, 2, 2, 4, 4, 7, 7, 7,  8,  0,  10, 8, 3,  4,  10, 10, 2,  4, 10,
     12, 7,  0, 0, 0, 0, 8, 8, 10, 10, 10, 8,  8, 8,  8,  8,  8,  8,  8},
    {4,  2,  1, 2, 0, 7, 7, 8, 2,  5,  7,  4,  5, 7, 5,  5,  3,  4, 4, 4,
     0,  4,  0, 0, 4, 8, 0, 0, 0,  2,  0,  10, 0, 8, 12, 0,  2,  4, 6, 6,
     4,  8,  8, 6, 6, 3, 8, 7, 8,  0,  6,  0,  6, 8, 10, 12, 10, 8, 6, 6,
     3,  12, 2, 2, 6, 6, 4, 4, 4,  10, 0,  12, 5, 3, 6,  12, 12, 2, 4, 7,
     10, 5,  0, 0, 0, 0, 6, 6, 12, 12, 12, 6,  6, 6, 6,  6,  6,  6, 6},
    {3,  2,  2, 1, 0, 5, 6, 7, 4,  5,  7,  4,  5, 7,  3,  4,  5,  6,  3, 2,
     0,  2,  0, 0, 5, 8, 0, 0, 0,  2,  0,  10, 0, 8,  10, 0,  2,  4,  5, 5,
     6,  4,  7, 4, 6, 5, 7, 6, 10, 0,  8,  0,  4, 10, 12, 14, 12, 10, 4, 3,
     5,  10, 2, 2, 4, 4, 7, 7, 7,  8,  0,  10, 8, 3,  4,  10, 10, 2,  4, 10,
     12, 7,  0, 0, 0, 0, 8, 8, 10, 10, 10, 8,  8, 8,  8,  8,  8,  8,  8},
    {0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {4,  5, 7, 5, 0, 1, 3,  5,  4, 5, 6, 5,  5,  6,  2,  4,  7,  7,  5, 4,
     0,  4, 0, 0, 7, 6, 0,  0,  0, 4, 0, 5,  0,  12, 5,  0,  6,  6,  4, 4,
     6,  4, 4, 4, 7, 6, 6,  6,  4, 0, 6, 0,  5,  12, 14, 16, 14, 12, 2, 4,
     8,  6, 6, 6, 4, 4, 8,  8,  8, 3, 0, 6,  6,  7,  6,  6,  8,  5,  4, 12,
     10, 8, 0, 0, 0, 0, 14, 14, 8, 8, 8, 14, 14, 14, 14, 14, 14, 14, 14},
    {6,  6, 7, 6, 0,  3, 1,  6,  3,  5, 6,  5,  5,  6,  4,  2,  7,  7,  7, 6,
     0,  6, 0, 0, 7,  6, 0,  0,  0,  4, 0,  4,  0,  12, 4,  0,  6,  6,  4, 4,
     7,  4, 4, 5, 10, 8, 6,  5,  10, 0, 10, 0,  5,  12, 14, 16, 14, 12, 3, 4,
     8,  6, 6, 6, 4,  4, 8,  8,  8,  4, 0,  6,  6,  8,  5,  6,  8,  6,  6, 12,
     10, 8, 0, 0, 0,  0, 14, 14, 8,  8, 8,  14, 14, 14, 14, 14, 14, 14, 14},
    {7,  7, 8, 7, 0, 5, 6,  1,  2,  6,  8,  7,  7,  8,  6,  7,  7,  7,  7, 7,
     0,  7, 0, 0, 7, 5, 0,  0,  0,  7,  0,  12, 0,  4,  12, 0,  7,  2,  4, 2,
     12, 3, 6, 7, 8, 8, 7,  7,  10, 0,  10, 0,  5,  4,  5,  5,  8,  4,  6, 6,
     6,  5, 7, 7, 4, 4, 7,  7,  7,  6,  0,  10, 10, 10, 6,  6,  10, 7,  4, 7,
     5,  7, 0, 0, 0, 0, 12, 12, 10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12},
    {5,  3, 2, 4, 0, 4, 3,  2,  1,  5,  8,  7,  7,  8,  6,  6,  7,  7,  7, 5,
     0,  5, 0, 0, 7, 6, 0,  0,  0,  5,  0,  10, 0,  5,  10, 0,  5,  2,  4, 2,
     10, 2, 5, 6, 8, 7, 5,  4,  10, 0,  8,  0,  4,  3,  3,  3,  6,  3,  5, 5,
     5,  4, 5, 5, 4, 4, 7,  7,  7,  7,  0,  10, 10, 10, 3,  7,  10, 5,  4, 6,
     4,  7, 0, 0, 0, 0, 12, 12, 10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12},
    {6, 5, 5, 5, 0, 5, 5,  6,  5,  1,  8,  7,  7,  8,  5,  7,  8,  8,  8, 6,
     0, 6, 0, 0, 8, 6, 0,  0,  0,  6,  0,  6,  0,  8,  6,  0,  6,  3,  3, 5,
     8, 3, 2, 6, 8, 7, 4,  5,  12, 0,  10, 0,  5,  6,  6,  8,  10, 5,  6, 6,
     6, 3, 6, 6, 4, 4, 7,  7,  7,  8,  0,  8,  8,  10, 6,  4,  10, 6,  4, 8,
     6, 7, 0, 0, 0, 0, 12, 12, 10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12},
    {7,  7,  7,  7,  0,  6,  6,  8,  8,  8,  1,  3,  4,  6,  6,  6,  10,
     10, 10, 8,  0,  8,  0,  0,  8,  10, 0,  0,  0,  8,  0,  3,  0,  12,
     3,  0,  7,  7,  7,  8,  4,  5,  6,  6,  10, 8,  8,  6,  12, 0,  12,
     0,  10, 12, 12, 14, 12, 12, 5,  6,  8,  5,  7,  7,  6,  6,  10, 10,
     10, 5,  0,  4,  6,  10, 6,  3,  5,  7,  6,  12, 14, 10, 0,  0,  0,
     0,  14, 14, 3,  4,  5,  14, 14, 14, 14, 14, 14, 14, 14},
    {4,  4,  4, 4, 0, 5, 5,  7,  7,  7, 3,  1,  2,  3,  4,  4,  8,  8,  6, 6,
     0,  6,  0, 0, 7, 8, 0,  0,  0,  6, 0,  4,  0,  10, 4,  0,  5,  5,  5, 6,
     5,  4,  5, 4, 8, 7, 7,  6,  10, 0, 10, 0,  10, 12, 12, 14, 12, 12, 4, 5,
     8,  6,  5, 5, 5, 5, 10, 10, 10, 6, 0,  4,  6,  10, 6,  4,  4,  5,  5, 12,
     14, 10, 0, 0, 0, 0, 12, 12, 4,  3, 3,  12, 12, 12, 12, 12, 12, 12, 12},
    {5,  5,  5, 5, 0, 5, 5,  7,  7,  7, 4,  2,  1,  2,  4,  4,  8,  8,  6, 6,
     0,  6,  0, 0, 7, 8, 0,  0,  0,  6, 0,  4,  0,  10, 4,  0,  5,  5,  5, 6,
     5,  4,  5, 4, 8, 7, 7,  6,  10, 0, 10, 0,  10, 12, 12, 14, 12, 12, 4, 5,
     8,  6,  5, 5, 5, 5, 10, 10, 10, 6, 0,  4,  6,  10, 6,  4,  4,  5,  5, 12,
     14, 10, 0, 0, 0, 0, 12, 12, 5,  3, 3,  12, 12, 12, 12, 12, 12, 12, 12},
    {7,  7,  7,  7,  0,  6,  6,  8,  8,  8,  6,  3,  2,  1,  4,  4,  7,
     7,  8,  8,  0,  8,  0,  0,  8,  10, 0,  0,  0,  8,  0,  6,  0,  12,
     6,  0,  7,  7,  7,  8,  5,  6,  6,  4,  10, 8,  8,  6,  10, 0,  10,
     0,  10, 12, 12, 14, 14, 14, 6,  6,  10, 6,  7,  7,  7,  7,  10, 10,
     10, 10, 0,  5,  7,  8,  8,  5,  5,  7,  7,  12, 14, 10, 0,  0,  0,
     0,  14, 14, 8,  5,  4,  14, 14, 14, 14, 14, 14, 14, 14},
    {3,  3,  5, 3, 0,  2, 4,  6,  6,  5, 6, 4,  4,  4,  1,  2,  5,  4,  5, 4,
     0,  4,  0, 0, 7,  6, 0,  0,  0,  4, 0, 6,  0,  12, 6,  0,  6,  6,  5, 5,
     6,  6,  5, 2, 10, 8, 6,  6,  5,  0, 7, 0,  8,  12, 12, 14, 12, 12, 3, 5,
     7,  5,  6, 6, 5,  5, 10, 10, 10, 5, 0, 3,  5,  3,  6,  7,  7,  6,  5, 12,
     14, 10, 0, 0, 0,  0, 14, 14, 10, 8, 7, 14, 14, 14, 14, 14, 14, 14, 14},
    {5,  4,  5, 4, 0,  4, 2,  7,  6,  7, 6, 4,  4,  4,  2,  1,  5,  5,  5, 5,
     0,  5,  0, 0, 7,  6, 0,  0,  0,  4, 0, 5,  0,  12, 5,  0,  5,  6,  5, 5,
     3,  5,  6, 3, 10, 8, 6,  4,  10, 0, 8, 0,  8,  12, 12, 14, 12, 12, 4, 4,
     8,  7,  5, 5, 5,  5, 10, 10, 10, 6, 0, 4,  7,  5,  6,  7,  8,  5,  5, 12,
     14, 10, 0, 0, 0,  0, 14, 14, 10, 8, 7, 14, 14, 14, 14, 14, 14, 14, 14},
    {6, 5,  3, 5, 0, 7, 7, 7, 7,  8,  10, 8,  8, 7, 5,  5,  1,  2, 5, 5,
     0, 5,  0, 0, 4, 5, 0, 0, 0,  4,  0,  10, 0, 8, 12, 0,  5,  6, 7, 7,
     5, 8,  8, 4, 6, 4, 8, 7, 7,  0,  6,  0,  6, 8, 10, 12, 10, 8, 6, 7,
     4, 10, 5, 5, 7, 7, 6, 6, 6,  8,  0,  10, 3, 2, 6,  12, 12, 5, 7, 7,
     8, 5,  0, 0, 0, 0, 6, 6, 12, 12, 12, 10, 8, 8, 8,  8,  8,  8, 8},
    {7, 6,  4,  6, 0, 7, 7, 7, 7,  8,  10, 8,  8, 7,  4,  5,  2,  1, 5, 6,
     0, 6,  0,  0, 3, 7, 0, 0, 0,  5,  0,  12, 0, 7,  12, 0,  5,  6, 8, 8,
     3, 10, 10, 5, 3, 4, 8, 8, 6,  0,  4,  0,  4, 10, 12, 14, 6,  7, 7, 8,
     5, 12, 5,  5, 8, 8, 3, 3, 3,  10, 0,  10, 2, 2,  7,  12, 12, 5, 8, 6,
     8, 3,  0,  0, 0, 0, 5, 5, 12, 12, 12, 10, 8, 8,  8,  8,  8,  8, 8},
    {3,  3,  4,  3, 0, 5, 7,  7,  7,  8,  10, 6,  6,  8,  5,  5,  5,  5,  1, 3,
     0,  3,  0,  0, 5, 6, 0,  0,  0,  3,  0,  10, 0,  10, 10, 0,  4,  6,  7, 7,
     8,  8,  10, 4, 8, 7, 8,  8,  10, 0,  10, 0,  6,  10, 12, 14, 12, 10, 4, 6,
     8,  12, 4,  4, 7, 7, 8,  8,  8,  8,  0,  10, 8,  5,  5,  10, 10, 4,  7, 10,
     12, 8,  0,  0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10},
    {2,  2,  4, 2, 0, 4, 6,  7,  5,  6,  8,  6,  6,  8,  4,  5,  5,  6,  3, 1,
     0,  2,  0, 0, 6, 7, 0,  0,  0,  3,  0,  10, 0,  10, 10, 0,  3,  6,  7, 6,
     8,  7,  8, 4, 7, 6, 8,  7,  10, 0,  10, 0,  6,  10, 12, 14, 12, 10, 4, 4,
     6,  10, 3, 3, 6, 6, 8,  8,  8,  8,  0,  10, 10, 4,  5,  10, 10, 3,  6, 10,
     12, 8,  0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {2,  2,  4, 2, 0, 4, 6,  7,  5,  6,  8,  6,  6,  8,  4,  5,  5,  6,  3, 2,
     0,  1,  0, 0, 6, 7, 0,  0,  0,  3,  0,  10, 0,  10, 10, 0,  3,  6,  7, 6,
     8,  7,  8, 4, 7, 6, 8,  7,  10, 0,  10, 0,  6,  10, 12, 14, 12, 10, 4, 4,
     6,  10, 3, 3, 6, 6, 8,  8,  8,  8,  0,  10, 10, 4,  5,  10, 10, 3,  6, 10,
     12, 8,  0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {6, 5,  4, 5, 0, 7, 7, 7, 7,  8,  8,  7,  7, 8, 7,  7,  4,  3, 5, 6,
     0, 6,  0, 0, 1, 3, 0, 0, 0,  4,  0,  10, 0, 6, 12, 0,  4,  4, 6, 5,
     4, 5,  7, 7, 4, 4, 8, 6, 8,  0,  7,  0,  5, 8, 10, 12, 10, 7, 7, 7,
     5, 12, 4, 4, 6, 6, 4, 4, 4,  10, 0,  12, 5, 4, 3,  12, 12, 4, 6, 6,
     8, 4,  0, 0, 0, 0, 6, 6, 12, 12, 12, 6,  6, 6, 6,  6,  6,  6, 6},
    {7,  8, 8, 8, 0,  6, 6,  5,  6,  6, 10, 8,  8,  10, 6,  6,  5,  7,  6, 7,
     0,  7, 0, 0, 3,  1, 0,  0,  0,  8, 0,  10, 0,  8,  10, 0,  7,  7,  6, 7,
     12, 4, 7, 7, 10, 8, 5,  10, 12, 0, 12, 0,  7,  6,  7,  7,  10, 6,  6, 6,
     8,  7, 7, 7, 6,  6, 8,  8,  8,  7, 0,  10, 10, 7,  4,  6,  8,  7,  6, 8,
     6,  8, 0, 0, 0,  0, 12, 12, 8,  8, 8,  12, 12, 12, 12, 12, 12, 12, 12},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {3,  2,  2, 2, 0, 4, 4, 7, 5,  6,  8,  6,  6, 8,  4,  4,  4,  5,  3, 3,
     0,  3,  0, 0, 4, 8, 0, 0, 0,  1,  0,  10, 0, 8,  10, 0,  2,  4,  5, 5,
     6,  4,  7, 4, 6, 5, 7, 7, 10, 0,  8,  0,  4, 10, 12, 14, 12, 10, 4, 3,
     5,  10, 2, 2, 4, 4, 7, 7, 7,  8,  0,  10, 8, 3,  4,  10, 10, 2,  4, 10,
     12, 7,  0, 0, 0, 0, 8, 8, 10, 10, 10, 8,  8, 8,  8,  8,  8,  8,  8},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {10, 10, 10, 10, 0,  5,  4,  12, 10, 6,  3,  4,  4,  6,  6,  5,  10,
     12, 10, 10, 0,  10, 0,  0,  10, 10, 0,  0,  0,  10, 0,  1,  0,  20,
     2,  0,  10, 12, 8,  10, 10, 4,  6,  8,  16, 14, 6,  6,  20, 0,  20,
     0,  12, 20, 20, 20, 20, 20, 7,  8,  14, 2,  10, 10, 8,  8,  16, 16,
     16, 5,  0,  2,  10, 8,  10, 2,  8,  10, 8,  20, 20, 16, 0,  0,  0,
     0,  20, 20, 4,  4,  4,  20, 20, 20, 20, 20, 20, 20, 20},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {10, 8,  8,  8,  0,  12, 12, 4,  5,  8,  12, 10, 10, 12, 12, 12, 8,
     7,  10, 10, 0,  10, 0,  0,  6,  8,  0,  0,  0,  8,  0,  20, 0,  1,
     20, 0,  14, 4,  7,  5,  16, 12, 12, 16, 6,  7,  10, 12, 3,  0,  4,
     0,  4,  2,  2,  2,  4,  2,  12, 14, 6,  20, 14, 14, 16, 16, 6,  6,
     6,  12, 0,  20, 16, 10, 8,  20, 20, 14, 16, 4,  2,  6,  0,  0,  0,
     0,  4,  4,  20, 20, 20, 4,  4,  4,  4,  4,  4,  4,  4},
    {10, 10, 12, 10, 0,  5,  4,  12, 10, 6,  3,  4,  4,  6,  6,  5,  12,
     12, 10, 10, 0,  10, 0,  0,  12, 10, 0,  0,  0,  10, 0,  2,  0,  20,
     1,  0,  10, 12, 8,  10, 10, 4,  6,  8,  16, 14, 6,  8,  20, 0,  20,
     0,  12, 20, 20, 20, 20, 20, 7,  10, 14, 2,  10, 10, 8,  8,  16, 16,
     16, 5,  0,  2,  10, 8,  10, 2,  8,  10, 8,  20, 20, 16, 0,  0,  0,
     0,  20, 20, 4,  4,  4,  20, 20, 20, 20, 20, 20, 20, 20},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {3,  2,  2, 2, 0, 6, 6,  7,  5,  6,  7,  5,  5,  7,  6,  5,  5,  5,  4, 3,
     0,  3,  0, 0, 4, 7, 0,  0,  0,  2,  0,  10, 0,  14, 10, 0,  1,  3,  5, 5,
     5,  5,  7, 4, 7, 5, 6,  8,  14, 0,  8,  0,  7,  8,  10, 12, 10, 8,  4, 7,
     7,  10, 1, 1, 5, 5, 7,  7,  7,  12, 0,  10, 6,  6,  7,  10, 10, 1,  5, 7,
     12, 7,  0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14},
    {6, 4,  4, 4, 0, 6, 6,  2,  2,  3,  7,  5,  5,  7,  6,  6,  6,  6,  6, 6,
     0, 6,  0, 0, 4, 7, 0,  0,  0,  4,  0,  12, 0,  4,  12, 0,  3,  1,  5, 3,
     8, 3,  8, 6, 7, 6, 7,  7,  7,  0,  6,  0,  4,  4,  5,  8,  8,  2,  6, 8,
     6, 12, 3, 3, 6, 6, 6,  6,  6,  6,  0,  12, 8,  8,  6,  12, 12, 3,  5, 8,
     6, 6,  0, 0, 0, 0, 12, 12, 10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12},
    {7,  5, 6, 5, 0, 4, 4,  4,  4,  3,  7,  5,  5,  7,  5,  5,  7,  8,  7, 7,
     0,  7, 0, 0, 6, 6, 0,  0,  0,  5,  0,  8,  0,  7,  8,  0,  5,  5,  1, 3,
     10, 5, 5, 4, 8, 7, 5,  10, 12, 0,  12, 0,  10, 12, 12, 14, 14, 12, 4, 8,
     8,  8, 4, 4, 2, 2, 8,  8,  8,  10, 0,  10, 10, 10, 6,  8,  10, 12, 4, 7,
     4,  8, 0, 0, 0, 0, 12, 12, 10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12},
    {6,  5, 6, 5, 0,  4, 4,  2,  2,  5,  8,  6,  6,  8,  5,  5,  7,  8,  7, 6,
     0,  6, 0, 0, 5,  7, 0,  0,  0,  5,  0,  10, 0,  5,  10, 0,  5,  3,  3, 1,
     10, 8, 8, 8, 10, 8, 5,  8,  10, 0,  10, 0,  8,  6,  5,  7,  8,  5,  8, 10,
     8,  8, 7, 7, 5,  5, 7,  7,  7,  8,  0,  10, 10, 10, 8,  10, 12, 7,  5, 6,
     5,  7, 0, 0, 0,  0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12},
    {8,  6,  4, 6,  0, 6,  7, 12, 10, 8,  4,  5,  5, 5,  6,  3,  5,  3,  8,  8,
     0,  8,  0, 0,  4, 12, 0, 0,  0,  6,  0,  10, 0, 16, 10, 0,  5,  8,  10, 10,
     1,  8,  8, 10, 4, 5,  8, 10, 12, 0,  12, 0,  8, 12, 12, 14, 10, 12, 10, 8,
     8,  10, 5, 5,  8, 8,  4, 4,  4,  8,  0,  8,  3, 6,  10, 4,  4,  5,  8,  10,
     12, 4,  0, 0,  0, 0,  8, 8,  12, 12, 12, 8,  8, 8,  8,  8,  8,  8,  8},
    {7,  4,  8, 4, 0, 4, 4,  3,  2,  3, 5,  4,  4,  6,  6,  5,  8,  10, 8, 7,
     0,  7,  0, 0, 5, 4, 0,  0,  0,  4, 0,  4,  0,  12, 4,  0,  5,  3,  5, 8,
     8,  1,  6, 7, 8, 7, 4,  6,  12, 0, 12, 0,  7,  8,  12, 12, 14, 12, 7, 5,
     7,  8,  5, 5, 4, 4, 10, 10, 10, 5, 0,  5,  8,  8,  3,  4,  8,  5,  4, 14,
     12, 10, 0, 0, 0, 0, 14, 14, 6,  6, 6,  14, 14, 14, 14, 14, 14, 14, 14},
    {8,  7,  8, 7, 0, 4, 4,  6,  5,  2, 6, 5,  5,  6,  5,  6,  8,  10, 10, 8,
     0,  8,  0, 0, 7, 7, 0,  0,  0,  7, 0, 6,  0,  12, 6,  0,  7,  8,  5,  8,
     8,  6,  1, 8, 8, 8, 6,  8,  10, 0, 8, 0,  7,  7,  8,  10, 12, 7,  8,  8,
     10, 3,  5, 5, 4, 4, 10, 10, 10, 6, 0, 7,  10, 10, 6,  4,  10, 5,  4,  12,
     10, 10, 0, 0, 0, 0, 12, 12, 8,  8, 8, 12, 12, 12, 12, 12, 12, 12, 12},
    {4,  4,  6, 4, 0,  4, 5,  7,  6,  6, 6, 4,  4,  4,  2,  3,  4,  5,  4, 4,
     0,  4,  0, 0, 7,  7, 0,  0,  0,  4, 0, 8,  0,  16, 8,  0,  4,  6,  4, 8,
     10, 7,  8, 1, 10, 8, 6,  8,  5,  0, 7, 0,  8,  10, 10, 12, 12, 10, 2, 7,
     10, 8,  4, 4, 3,  3, 10, 10, 10, 6, 0, 6,  8,  8,  7,  7,  10, 4,  3, 12,
     12, 10, 0, 0, 0,  0, 14, 14, 8,  8, 8, 14, 14, 14, 14, 14, 14, 14, 14},
    {7,  6,  6, 6,  0, 7,  10, 8,  8,  8,  10, 8,  8, 10, 10, 10, 6,  3, 8,  7,
     0,  7,  0, 0,  4, 10, 0,  0,  0,  6,  0,  16, 0, 6,  16, 0,  7,  7, 8,  10,
     4,  8,  8, 10, 1, 3,  10, 10, 4,  0,  4,  0,  4, 8,  10, 12, 12, 8, 10, 12,
     8,  12, 7, 7,  8, 8,  4,  4,  4,  10, 0,  12, 4, 5,  7,  10, 8,  7, 8,  12,
     12, 4,  0, 0,  0, 0,  8,  8,  12, 12, 12, 8,  8, 8,  8,  8,  8,  8, 8},
    {6,  5, 3, 5, 0, 6, 8, 8, 7,  7,  8,  7,  7, 8, 8,  8,  4,  4, 7, 6,
     0,  6, 0, 0, 4, 8, 0, 0, 0,  5,  0,  14, 0, 7, 14, 0,  5,  6, 7, 8,
     5,  7, 8, 8, 3, 1, 8, 8, 8,  0,  7,  0,  6, 6, 8,  10, 10, 8, 6, 8,
     6,  8, 5, 5, 8, 8, 3, 3, 3,  10, 0,  12, 3, 4, 7,  10, 8,  5, 8, 10,
     10, 3, 0, 0, 0, 0, 8, 8, 14, 14, 14, 8,  8, 8, 8,  8,  8,  8, 8},
    {8,  7, 8, 7, 0,  6, 6,  7,  5,  4, 8,  7,  7,  8,  6,  6,  8,  8,  8, 8,
     0,  8, 0, 0, 8,  5, 0,  0,  0,  7, 0,  6,  0,  10, 6,  0,  6,  7,  5, 5,
     8,  4, 6, 6, 10, 8, 1,  4,  10, 0, 10, 0,  5,  8,  8,  10, 12, 8,  6, 5,
     8,  5, 6, 6, 5,  5, 8,  8,  8,  6, 0,  6,  8,  8,  5,  4,  8,  6,  5, 12,
     10, 8, 0, 0, 0,  0, 14, 14, 8,  8, 8,  14, 14, 14, 14, 14, 14, 14, 14},
    {7,  6,  7,  6,  0,  6,  5,  7,  4,  5,  6,  6,  6,  6,  6,  4,  7,
     8,  8,  7,  0,  7,  0,  0,  6,  10, 0,  0,  0,  7,  0,  6,  0,  12,
     8,  0,  8,  7,  10, 8,  10, 6,  8,  8,  10, 8,  4,  1,  10, 0,  10,
     0,  8,  10, 10, 12, 14, 10, 6,  7,  10, 7,  8,  8,  6,  6,  10, 10,
     10, 8,  0,  6,  10, 10, 4,  7,  10, 8,  6,  14, 12, 10, 0,  0,  0,
     0,  14, 14, 8,  8,  8,  14, 14, 14, 14, 14, 14, 14, 14},
    {10, 10, 8,  10, 0,  4,  10, 10, 10, 12, 12, 10, 10, 10, 5,  10, 7,
     6,  10, 10, 0,  10, 0,  0,  8,  12, 0,  0,  0,  10, 0,  20, 0,  3,
     20, 0,  14, 7,  12, 10, 12, 12, 10, 5,  4,  8,  10, 10, 1,  0,  2,
     0,  8,  4,  4,  5,  6,  3,  4,  8,  6,  20, 14, 14, 16, 16, 4,  4,
     4,  3,  0,  20, 8,  7,  10, 20, 16, 14, 16, 6,  5,  4,  0,  0,  0,
     0,  6,  6,  20, 20, 20, 6,  6,  6,  6,  6,  6,  6,  6},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {10, 8,  6,  8,  0,  6,  10, 10, 8,  10, 12, 10, 10, 10, 7,  8, 6,
     4,  10, 10, 0,  10, 0,  0,  7,  12, 0,  0,  0,  8,  0,  20, 0, 4,
     20, 0,  8,  6,  12, 10, 12, 12, 8,  7,  4,  7,  10, 10, 2,  0, 1,
     0,  6,  2,  5,  7,  8,  5,  8,  8,  12, 20, 8,  8,  10, 10, 7, 7,
     7,  6,  0,  20, 10, 10, 8,  20, 16, 8,  10, 8,  7,  7,  0,  0, 0,
     0,  6,  6,  20, 20, 20, 6,  6,  6,  6,  6,  6,  6,  6},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {6, 4,  6, 4, 0, 5, 5, 5, 4,  5,  10, 10, 10, 10, 8,  8,  6,  4, 6,  6,
     0, 6,  0, 0, 5, 7, 0, 0, 0,  4,  0,  12, 0,  4,  12, 0,  7,  4, 10, 8,
     8, 7,  7, 8, 4, 6, 5, 8, 8,  0,  6,  0,  1,  4,  6,  8,  10, 4, 10, 8,
     5, 14, 7, 7, 8, 8, 4, 4, 4,  10, 0,  12, 6,  6,  8,  12, 10, 7, 8,  10,
     8, 4,  0, 0, 0, 0, 8, 8, 14, 14, 14, 8,  8,  8,  8,  8,  8,  8, 8},
    {10, 10, 8,  10, 0,  12, 12, 4,  3,  6,  12, 12, 12, 12, 12, 12, 8,
     10, 10, 10, 0,  10, 0,  0,  8,  6,  0,  0,  0,  10, 0,  20, 0,  2,
     20, 0,  8,  4,  12, 6,  12, 8,  7,  10, 8,  6,  8,  10, 4,  0,  2,
     0,  4,  1,  2,  2,  4,  2,  10, 10, 6,  20, 8,  8,  10, 10, 4,  4,
     4,  10, 0,  20, 10, 10, 8,  20, 16, 8,  10, 4,  2,  4,  0,  0,  0,
     0,  5,  5,  20, 20, 20, 5,  5,  5,  5,  5,  5,  5,  5},
    {12, 12, 10, 12, 0,  14, 14, 5,  3,  6,  12, 12, 12, 12, 12, 12, 10,
     12, 12, 12, 0,  12, 0,  0,  10, 7,  0,  0,  0,  12, 0,  20, 0,  2,
     20, 0,  10, 5,  12, 5,  12, 12, 8,  10, 10, 8,  8,  10, 4,  0,  5,
     0,  6,  2,  1,  2,  4,  2,  10, 10, 6,  20, 8,  8,  10, 10, 4,  4,
     4,  10, 0,  20, 10, 10, 8,  20, 16, 8,  10, 4,  2,  4,  0,  0,  0,
     0,  5,  5,  20, 20, 20, 5,  5,  5,  5,  5,  5,  5,  5},
    {14, 14, 12, 14, 0,  16, 16, 5,  3,  8,  14, 14, 14, 14, 14, 14, 12,
     14, 14, 14, 0,  14, 0,  0,  12, 7,  0,  0,  0,  14, 0,  20, 0,  2,
     20, 0,  12, 8,  14, 7,  14, 12, 10, 12, 12, 10, 10, 12, 5,  0,  7,
     0,  8,  2,  2,  1,  3,  3,  12, 12, 8,  20, 10, 10, 12, 12, 8,  8,
     8,  12, 0,  20, 14, 14, 12, 20, 20, 10, 12, 4,  2,  8,  0,  0,  0,
     0,  8,  8,  20, 20, 20, 8,  8,  8,  8,  8,  8,  8,  8},
    {12, 12, 10, 12, 0,  14, 14, 8,  6,  10, 12, 12, 12, 14, 12, 12, 10,
     6,  12, 12, 0,  12, 0,  0,  10, 10, 0,  0,  0,  12, 0,  20, 0,  4,
     20, 0,  10, 8,  14, 8,  10, 14, 12, 12, 12, 10, 12, 14, 6,  0,  8,
     0,  10, 4,  4,  3,  1,  5,  12, 12, 10, 20, 8,  8,  14, 14, 10, 10,
     10, 12, 0,  20, 12, 12, 12, 20, 20, 8,  14, 2,  4,  10, 0,  0,  0,
     0,  8,  8,  20, 20, 20, 8,  8,  8,  8,  8,  8,  8,  8},
    {10, 10, 8,  10, 0,  12, 12, 4,  3,  5,  12, 12, 12, 14, 12, 12, 8,
     7,  10, 10, 0,  10, 0,  0,  7,  6,  0,  0,  0,  10, 0,  20, 0,  2,
     20, 0,  8,  2,  12, 5,  12, 12, 7,  10, 8,  8,  8,  10, 3,  0,  5,
     0,  4,  2,  2,  3,  5,  1,  10, 12, 6,  20, 8,  8,  12, 12, 8,  8,
     8,  12, 0,  20, 12, 12, 10, 20, 20, 8,  12, 5,  3,  8,  0,  0,  0,
     0,  10, 10, 20, 20, 20, 10, 10, 10, 10, 10, 10, 10, 10},
    {4,  4,  6, 4, 0,  2, 3,  6,  5,  6, 5, 4,  4,  6,  3,  4,  6,  7,  4, 4,
     0,  4,  0, 0, 7,  6, 0,  0,  0,  4, 0, 7,  0,  12, 7,  0,  4,  6,  4, 8,
     10, 7,  8, 2, 10, 6, 6,  6,  4,  0, 8, 0,  10, 10, 10, 12, 12, 10, 1, 7,
     10, 8,  4, 4, 3,  3, 10, 10, 10, 4, 0, 7,  10, 10, 7,  7,  10, 4,  3, 12,
     12, 10, 0, 0, 0,  0, 14, 14, 7,  7, 7, 14, 14, 14, 14, 14, 14, 14, 14},
    {4,  3,  6, 3, 0,  4, 4,  6,  5,  6, 6, 5,  5,  6,  5,  4,  7,  8,  6, 4,
     0,  4,  0, 0, 7,  6, 0,  0,  0,  3, 0, 8,  0,  14, 10, 0,  7,  8,  8, 10,
     8,  5,  8, 7, 12, 8, 5,  7,  8,  0, 8, 0,  8,  10, 10, 12, 12, 12, 7, 1,
     5,  7,  7, 7, 7,  7, 12, 12, 12, 8, 0, 8,  10, 10, 7,  6,  10, 7,  7, 8,
     12, 12, 0, 0, 0,  0, 14, 14, 8,  8, 8, 14, 14, 14, 14, 14, 14, 14, 14},
    {6, 5,  3,  5,  0, 8, 8, 6,  5,  6,  8,  8,  8, 10, 7,  8,  4,  5, 8,  6,
     0, 6,  0,  0,  5, 8, 0, 0,  0,  5,  0,  14, 0, 6,  14, 0,  7,  6, 8,  8,
     8, 7,  10, 10, 8, 6, 8, 10, 6,  0,  12, 0,  5, 6,  6,  8,  10, 6, 10, 5,
     1, 10, 7,  7,  8, 8, 3, 3,  3,  8,  0,  14, 5, 5,  10, 10, 10, 7, 8,  10,
     8, 10, 0,  0,  0, 0, 8, 8,  16, 16, 16, 8,  8, 8,  8,  8,  8,  8, 8},
    {10, 10, 12, 10, 0,  6,  6,  5,  4,  3,  5,  6,  6,  6,  5,  7,  10,
     12, 12, 10, 0,  10, 0,  0,  12, 7,  0,  0,  0,  10, 0,  2,  0,  20,
     2,  0,  10, 12, 8,  8,  10, 8,  3,  8,  12, 8,  5,  7,  20, 0,  20,
     0,  14, 20, 20, 20, 20, 20, 8,  7,  10, 1,  10, 10, 8,  8,  20, 20,
     20, 5,  0,  2,  10, 10, 7,  2,  6,  10, 8,  20, 20, 20, 0,  0,  0,
     0,  20, 20, 4,  4,  4,  20, 20, 20, 20, 20, 20, 20, 20},
    {3,  2,  2, 2, 0, 6, 6,  7,  5,  6,  7,  5,  5,  7,  6,  5,  5,  5,  4, 3,
     0,  3,  0, 0, 4, 7, 0,  0,  0,  2,  0,  10, 0,  14, 10, 0,  1,  3,  4, 7,
     5,  5,  5, 4, 7, 5, 6,  8,  14, 0,  8,  0,  7,  8,  8,  10, 8,  8,  4, 7,
     7,  10, 1, 2, 5, 5, 7,  7,  7,  12, 0,  10, 6,  6,  7,  10, 10, 2,  5, 10,
     12, 7,  0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14},
    {3,  2,  2, 2, 0, 6, 6,  7,  5,  6,  7,  5,  5,  7,  6,  5,  5,  5,  4, 3,
     0,  3,  0, 0, 4, 7, 0,  0,  0,  2,  0,  10, 0,  14, 10, 0,  1,  3,  4, 7,
     5,  5,  5, 4, 7, 5, 6,  8,  14, 0,  8,  0,  7,  8,  8,  10, 8,  8,  4, 7,
     7,  10, 2, 1, 5, 5, 7,  7,  7,  12, 0,  10, 6,  6,  7,  10, 10, 2,  5, 10,
     12, 7,  0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14},
    {6, 4, 6, 4, 0, 4, 4,  4,  4,  4,  6,  5,  5,  7,  5,  5,  7,  8,  7, 6,
     0, 6, 0, 0, 6, 6, 0,  0,  0,  4,  0,  8,  0,  16, 8,  0,  5,  6,  2, 5,
     8, 4, 4, 3, 8, 8, 5,  6,  16, 0,  10, 0,  8,  10, 10, 12, 14, 12, 3, 7,
     8, 8, 5, 5, 1, 2, 6,  6,  6,  10, 0,  8,  6,  6,  6,  8,  10, 5,  2, 6,
     4, 6, 0, 0, 0, 0, 12, 12, 10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12},
    {6, 4, 6, 4, 0, 4, 4,  4,  4,  4,  6,  5,  5,  7,  5,  5,  7,  8,  7, 6,
     0, 6, 0, 0, 6, 6, 0,  0,  0,  4,  0,  8,  0,  16, 8,  0,  5,  6,  2, 5,
     8, 4, 4, 3, 8, 8, 5,  6,  16, 0,  10, 0,  8,  10, 10, 12, 14, 12, 3, 7,
     8, 8, 5, 5, 2, 1, 6,  6,  6,  10, 0,  8,  6,  6,  6,  8,  10, 5,  2, 6,
     4, 6, 0, 0, 0, 0, 12, 12, 10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12},
    {8,  7,  4,  7,  0,  8,  8,  7,  7,  7,  10, 10, 10, 10, 10, 10, 6,
     3,  8,  8,  0,  8,  0,  0,  4,  8,  0,  0,  0,  7,  0,  16, 0,  6,
     16, 0,  7,  6,  8,  7,  4,  10, 10, 10, 4,  3,  8,  10, 4,  0,  7,
     0,  4,  4,  4,  8,  10, 8,  10, 12, 3,  20, 7,  7,  6,  6,  1,  2,
     2,  10, 0,  20, 5,  5,  10, 20, 16, 7,  6,  8,  6,  2,  0,  0,  0,
     0,  10, 10, 20, 20, 20, 10, 10, 10, 10, 10, 10, 10, 10},
    {8,  7,  4,  7,  0,  8,  8,  7,  7,  7,  10, 10, 10, 10, 10, 10, 6,
     3,  8,  8,  0,  8,  0,  0,  4,  8,  0,  0,  0,  7,  0,  16, 0,  6,
     16, 0,  7,  6,  8,  7,  4,  10, 10, 10, 4,  3,  8,  10, 4,  0,  7,
     0,  4,  4,  4,  8,  10, 8,  10, 12, 3,  20, 7,  7,  6,  6,  2,  1,
     2,  10, 0,  20, 5,  5,  10, 20, 16, 7,  6,  8,  6,  2,  0,  0,  0,
     0,  10, 10, 20, 20, 20, 10, 10, 10, 10, 10, 10, 10, 10},
    {8,  7,  4,  7,  0,  8,  8,  7,  7,  7,  10, 10, 10, 10, 10, 10, 6,
     3,  8,  8,  0,  8,  0,  0,  4,  8,  0,  0,  0,  7,  0,  16, 0,  6,
     16, 0,  7,  6,  8,  7,  4,  10, 10, 10, 4,  3,  8,  10, 4,  0,  7,
     0,  4,  4,  4,  8,  10, 8,  10, 12, 3,  20, 7,  7,  6,  6,  2,  2,
     1,  10, 0,  20, 5,  5,  10, 20, 16, 7,  6,  8,  6,  2,  0,  0,  0,
     0,  10, 10, 20, 20, 20, 10, 10, 10, 10, 10, 10, 10, 10},
    {8,  8,  10, 8,  0,  3,  4,  6,  7,  8,  5,  6,  6,  10, 5,  6,  8,
     10, 8,  8,  0,  8,  0,  0,  10, 7,  0,  0,  0,  8,  0,  5,  0,  12,
     5,  0,  12, 6,  10, 8,  8,  5,  6,  6,  10, 10, 6,  8,  3,  0,  6,
     0,  10, 10, 10, 12, 12, 12, 4,  8,  8,  5,  12, 12, 10, 10, 10, 10,
     10, 1,  0,  4,  7,  7,  6,  5,  8,  12, 10, 12, 14, 10, 0,  0,  0,
     0,  10, 10, 5,  4,  4,  10, 10, 10, 10, 10, 10, 10, 10},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {10, 10, 12, 10, 0,  6,  6,  10, 10, 8,  4,  4,  4,  5,  3,  4,  10,
     10, 10, 10, 0,  10, 0,  0,  12, 10, 0,  0,  0,  10, 0,  2,  0,  20,
     2,  0,  10, 12, 10, 10, 8,  5,  7,  6,  12, 12, 6,  6,  20, 0,  20,
     0,  12, 20, 20, 20, 20, 20, 7,  8,  14, 2,  10, 10, 8,  8,  20, 20,
     20, 4,  0,  1,  6,  6,  8,  2,  6,  10, 10, 20, 20, 20, 0,  0,  0,
     0,  20, 20, 4,  3,  3,  20, 20, 20, 20, 20, 20, 20, 20},
    {10, 8,  5,  8, 0, 6,  6, 10, 10, 8,  6,  6,  6, 7,  5,  7,  3,  2,  8,  10,
     0,  10, 0,  0, 5, 10, 0, 0,  0,  8,  0,  10, 0, 16, 10, 0,  6,  8,  10, 10,
     3,  8,  10, 8, 4, 3,  8, 10, 8,  0,  10, 0,  6, 10, 10, 14, 12, 12, 10, 10,
     5,  10, 6,  6, 6, 6,  5, 5,  5,  7,  0,  6,  1, 3,  8,  7,  5,  6,  10, 12,
     14, 5,  0,  0, 0, 0,  8, 8,  14, 14, 14, 8,  8, 8,  8,  8,  8,  8,  8},
    {4, 3,  3,  3, 0, 7, 8, 10, 10, 10, 10, 10, 10, 8,  3,  5,  2,  2,  5,  4,
     0, 4,  0,  0, 4, 7, 0, 0,  0,  3,  0,  8,  0,  10, 8,  0,  6,  8,  10, 10,
     6, 8,  10, 8, 5, 4, 8, 10, 7,  0,  10, 0,  6,  10, 10, 14, 12, 12, 10, 10,
     5, 10, 6,  6, 6, 6, 5, 5,  5,  7,  0,  6,  3,  1,  7,  10, 7,  6,  10, 7,
     8, 5,  0,  0, 0, 0, 8, 8,  14, 14, 14, 8,  8,  8,  8,  8,  8,  8,  8},
    {5,  4,  6, 4, 0, 6, 5,  6,  3,  6, 6, 6,  6,  8,  6,  6,  6,  7,  5, 5,
     0,  5,  0, 0, 3, 4, 0,  0,  0,  4, 0, 10, 0,  8,  10, 0,  7,  6,  6, 8,
     10, 3,  6, 7, 7, 7, 5,  4,  10, 0, 8, 0,  8,  8,  8,  12, 12, 10, 7, 7,
     10, 7,  7, 7, 6, 6, 10, 10, 10, 6, 0, 8,  8,  7,  1,  7,  8,  7,  6, 10,
     8,  10, 0, 0, 0, 0, 10, 10, 7,  7, 7, 10, 10, 10, 10, 10, 10, 10, 10},
    {10, 10, 12, 10, 0,  6,  6,  6,  7,  4,  3,  4,  4,  5,  7,  7,  12,
     12, 10, 10, 0,  10, 0,  0,  12, 6,  0,  0,  0,  10, 0,  2,  0,  20,
     2,  0,  10, 12, 8,  10, 4,  4,  4,  7,  10, 10, 4,  7,  20, 0,  20,
     0,  12, 20, 20, 20, 20, 20, 7,  6,  10, 2,  10, 10, 8,  8,  20, 20,
     20, 5,  0,  2,  7,  10, 7,  1,  8,  10, 8,  20, 20, 20, 0,  0,  0,
     0,  20, 20, 4,  4,  4,  20, 20, 20, 20, 20, 20, 20, 20},
    {10, 10, 12, 10, 0,  8,  8,  10, 10, 10, 5,  4,  4,  5,  7,  8,  12,
     12, 10, 10, 0,  10, 0,  0,  12, 8,  0,  0,  0,  10, 0,  8,  0,  20,
     8,  0,  10, 12, 10, 12, 4,  8,  10, 10, 8,  8,  8,  10, 16, 0,  16,
     0,  10, 16, 16, 20, 20, 20, 10, 10, 10, 6,  10, 10, 10, 10, 16, 16,
     16, 8,  0,  6,  5,  7,  8,  8,  1,  10, 10, 20, 20, 16, 0,  0,  0,
     0,  10, 10, 8,  6,  7,  10, 10, 10, 10, 10, 10, 10, 10},
    {3, 2,  2, 2, 0, 5, 6,  7,  5,  6,  7,  5,  5,  7,  6,  5,  5,  5,  4,  3,
     0, 3,  0, 0, 4, 7, 0,  0,  0,  2,  0,  10, 0,  14, 10, 0,  1,  3,  12, 7,
     5, 5,  5, 4, 7, 5, 6,  8,  14, 0,  8,  0,  7,  8,  8,  10, 8,  8,  4,  7,
     7, 10, 2, 2, 5, 5, 7,  7,  7,  12, 0,  10, 6,  6,  7,  10, 10, 1,  5,  3,
     6, 7,  0, 0, 0, 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14},
    {6, 4, 4, 4, 0, 4, 6,  4,  4,  4,  6,  5,  5,  7,  5,  5,  7,  8,  7, 6,
     0, 6, 0, 0, 6, 6, 0,  0,  0,  4,  0,  8,  0,  16, 8,  0,  5,  5,  4, 5,
     8, 4, 4, 3, 8, 8, 5,  6,  16, 0,  10, 0,  8,  10, 10, 12, 14, 12, 3, 7,
     8, 8, 5, 5, 2, 2, 6,  6,  6,  10, 0,  10, 10, 10, 6,  8,  10, 5,  1, 6,
     4, 6, 0, 0, 0, 0, 12, 12, 10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12},
    {10, 10, 7,  10, 0,  12, 12, 7,  6,  8,  12, 12, 12, 12, 12, 12, 7,
     6,  10, 10, 0,  10, 0,  0,  6,  8,  0,  0,  0,  10, 0,  20, 0,  4,
     20, 0,  7,  8,  7,  6,  10, 14, 12, 12, 12, 10, 12, 14, 6,  0,  8,
     0,  10, 4,  4,  4,  2,  5,  12, 8,  10, 20, 10, 10, 6,  6,  8,  8,
     8,  12, 0,  20, 12, 7,  10, 20, 20, 3,  6,  1,  3,  8,  0,  0,  0,
     0,  8,  8,  20, 20, 20, 8,  8,  8,  8,  8,  8,  8,  8},
    {12, 12, 10, 12, 0,  10, 10, 5,  4,  6,  14, 14, 14, 14, 14, 14, 8,
     8,  12, 12, 0,  12, 0,  0,  8,  6,  0,  0,  0,  12, 0,  20, 0,  2,
     20, 0,  12, 6,  4,  5,  12, 12, 10, 12, 12, 10, 10, 12, 5,  0,  7,
     0,  8,  2,  2,  2,  4,  3,  12, 12, 8,  20, 12, 12, 4,  4,  6,  6,
     6,  14, 0,  20, 14, 8,  8,  20, 20, 6,  4,  3,  1,  6,  0,  0,  0,
     0,  5,  5,  20, 20, 20, 5,  5,  5,  5,  5,  5,  5,  5},
    {8,  7,  5,  7,  0,  8,  8,  7,  7,  7,  10, 10, 10, 10, 10, 10, 5,
     3,  8,  8,  0,  8,  0,  0,  4,  8,  0,  0,  0,  7,  0,  16, 0,  6,
     16, 0,  7,  6,  8,  7,  4,  10, 10, 10, 4,  3,  8,  10, 4,  0,  7,
     0,  4,  4,  4,  8,  10, 8,  10, 12, 10, 20, 7,  7,  6,  6,  2,  2,
     2,  10, 0,  20, 5,  5,  10, 20, 16, 7,  6,  8,  6,  1,  0,  0,  0,
     0,  10, 10, 20, 20, 20, 10, 10, 10, 10, 10, 10, 10, 10},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {10, 8,  6,  8,  0,  14, 14, 12, 12, 12, 14, 12, 12, 14, 14, 14, 6,
     5,  10, 10, 0,  10, 0,  0,  6,  12, 0,  0,  0,  8,  0,  20, 0,  4,
     20, 0,  14, 12, 12, 12, 8,  14, 12, 14, 8,  8,  14, 14, 6,  0,  6,
     0,  8,  5,  5,  8,  8,  10, 14, 14, 8,  20, 14, 14, 12, 12, 10, 10,
     10, 10, 0,  20, 8,  8,  10, 20, 10, 14, 12, 8,  5,  10, 0,  0,  0,
     0,  1,  2,  20, 20, 20, 6,  5,  4,  4,  4,  4,  4,  4},
    {10, 8,  6,  8,  0,  14, 14, 12, 12, 12, 14, 12, 12, 14, 14, 14, 6,
     5,  10, 10, 0,  10, 0,  0,  6,  12, 0,  0,  0,  8,  0,  20, 0,  4,
     20, 0,  14, 12, 12, 12, 8,  14, 12, 14, 8,  8,  14, 14, 6,  0,  6,
     0,  8,  5,  5,  8,  8,  10, 14, 14, 8,  20, 14, 14, 12, 12, 10, 10,
     10, 10, 0,  20, 8,  8,  10, 20, 10, 14, 12, 8,  5,  10, 0,  0,  0,
     0,  2,  1,  20, 20, 20, 7,  6,  5,  5,  5,  5,  5,  5},
    {10, 10, 12, 10, 0,  8,  8,  10, 10, 10, 3,  4,  5,  8,  10, 10, 12,
     12, 10, 10, 0,  10, 0,  0,  12, 8,  0,  0,  0,  10, 0,  4,  0,  20,
     4,  0,  14, 10, 10, 12, 12, 6,  8,  8,  12, 14, 8,  8,  20, 0,  20,
     0,  14, 20, 20, 20, 20, 20, 7,  8,  16, 4,  14, 14, 10, 10, 20, 20,
     20, 5,  0,  4,  14, 14, 7,  4,  8,  14, 10, 20, 20, 20, 0,  0,  0,
     0,  20, 20, 1,  3,  4,  20, 20, 20, 20, 20, 20, 20, 20},
    {10, 10, 12, 10, 0,  8,  8,  10, 10, 10, 4,  3,  3,  5,  8,  8,  12,
     12, 10, 10, 0,  10, 0,  0,  12, 8,  0,  0,  0,  10, 0,  4,  0,  20,
     4,  0,  14, 10, 10, 12, 12, 6,  8,  8,  12, 14, 8,  8,  20, 0,  20,
     0,  14, 20, 20, 20, 20, 20, 7,  8,  16, 4,  14, 14, 10, 10, 20, 20,
     20, 4,  0,  3,  14, 14, 7,  4,  6,  14, 10, 20, 20, 20, 0,  0,  0,
     0,  20, 20, 3,  1,  2,  20, 20, 20, 20, 20, 20, 20, 20},
    {10, 10, 12, 10, 0,  8,  8,  10, 10, 10, 5,  3,  3,  4,  7,  7,  12,
     12, 10, 10, 0,  10, 0,  0,  12, 8,  0,  0,  0,  10, 0,  4,  0,  20,
     4,  0,  14, 10, 10, 12, 12, 6,  8,  8,  12, 14, 8,  8,  20, 0,  20,
     0,  14, 20, 20, 20, 20, 20, 7,  8,  16, 4,  14, 14, 10, 10, 20, 20,
     20, 4,  0,  3,  14, 14, 7,  4,  7,  14, 10, 20, 20, 20, 0,  0,  0,
     0,  20, 20, 4,  2,  1,  20, 20, 20, 20, 20, 20, 20, 20},
    {10, 8,  6,  8,  0,  14, 14, 12, 12, 12, 14, 12, 12, 14, 14, 14, 10,
     10, 10, 10, 0,  10, 0,  0,  6,  12, 0,  0,  0,  8,  0,  20, 0,  4,
     20, 0,  14, 12, 12, 12, 8,  14, 12, 14, 8,  8,  14, 14, 6,  0,  6,
     0,  8,  5,  5,  8,  8,  10, 14, 14, 8,  20, 14, 14, 12, 12, 10, 10,
     10, 10, 0,  20, 8,  8,  10, 20, 10, 14, 12, 8,  5,  10, 0,  0,  0,
     0,  6,  7,  20, 20, 20, 1,  2,  3,  5,  5,  5,  5,  5},
    {10, 8,  6,  8,  0,  14, 14, 12, 12, 12, 14, 12, 12, 14, 14, 14, 8,
     8,  10, 10, 0,  10, 0,  0,  6,  12, 0,  0,  0,  8,  0,  20, 0,  4,
     20, 0,  14, 12, 12, 12, 8,  14, 12, 14, 8,  8,  14, 14, 6,  0,  6,
     0,  8,  5,  5,  8,  8,  10, 14, 14, 8,  20, 14, 14, 12, 12, 10, 10,
     10, 10, 0,  20, 8,  8,  10, 20, 10, 14, 12, 8,  5,  10, 0,  0,  0,
     0,  5,  6,  20, 20, 20, 2,  1,  2,  4,  4,  4,  4,  4},
    {10, 8,  6,  8,  0,  14, 14, 12, 12, 12, 14, 12, 12, 14, 14, 14, 8,
     8,  10, 10, 0,  10, 0,  0,  6,  12, 0,  0,  0,  8,  0,  20, 0,  4,
     20, 0,  14, 12, 12, 12, 8,  14, 12, 14, 8,  8,  14, 14, 6,  0,  6,
     0,  8,  5,  5,  8,  8,  10, 14, 14, 8,  20, 14, 14, 12, 12, 10, 10,
     10, 10, 0,  20, 8,  8,  10, 20, 10, 14, 12, 8,  5,  10, 0,  0,  0,
     0,  4,  5,  20, 20, 20, 3,  2,  1,  4,  4,  4,  4,  4},
    {10, 8,  6,  8,  0,  14, 14, 12, 12, 12, 14, 12, 12, 14, 14, 14, 8,
     8,  10, 10, 0,  10, 0,  0,  6,  12, 0,  0,  0,  8,  0,  20, 0,  4,
     20, 0,  14, 12, 12, 12, 8,  14, 12, 14, 8,  8,  14, 14, 6,  0,  6,
     0,  8,  5,  5,  8,  8,  10, 14, 14, 8,  20, 14, 14, 12, 12, 10, 10,
     10, 10, 0,  20, 8,  8,  10, 20, 10, 14, 12, 8,  5,  10, 0,  0,  0,
     0,  4,  5,  20, 20, 20, 5,  4,  4,  1,  3,  3,  3,  3},
    {10, 8,  6,  8,  0,  14, 14, 12, 12, 12, 14, 12, 12, 14, 14, 14, 8,
     8,  10, 10, 0,  10, 0,  0,  6,  12, 0,  0,  0,  8,  0,  20, 0,  4,
     20, 0,  14, 12, 12, 12, 8,  14, 12, 14, 8,  8,  14, 14, 6,  0,  6,
     0,  8,  5,  5,  8,  8,  10, 14, 14, 8,  20, 14, 14, 12, 12, 10, 10,
     10, 10, 0,  20, 8,  8,  10, 20, 10, 14, 12, 8,  5,  10, 0,  0,  0,
     0,  4,  5,  20, 20, 20, 5,  4,  4,  3,  1,  5,  4,  2},
    {10, 8,  6,  8,  0,  14, 14, 12, 12, 12, 14, 12, 12, 14, 14, 14, 8,
     8,  10, 10, 0,  10, 0,  0,  6,  12, 0,  0,  0,  8,  0,  20, 0,  4,
     20, 0,  14, 12, 12, 12, 8,  14, 12, 14, 8,  8,  14, 14, 6,  0,  6,
     0,  8,  5,  5,  8,  8,  10, 14, 14, 8,  20, 14, 14, 12, 12, 10, 10,
     10, 10, 0,  20, 8,  8,  10, 20, 10, 14, 12, 8,  5,  10, 0,  0,  0,
     0,  4,  5,  20, 20, 20, 5,  4,  4,  3,  5,  1,  3,  4},
    {10, 8,  6,  8,  0,  14, 14, 12, 12, 12, 14, 12, 12, 14, 14, 14, 8,
     8,  10, 10, 0,  10, 0,  0,  6,  12, 0,  0,  0,  8,  0,  20, 0,  4,
     20, 0,  14, 12, 12, 12, 8,  14, 12, 14, 8,  8,  14, 14, 6,  0,  6,
     0,  8,  5,  5,  8,  8,  10, 14, 14, 8,  20, 14, 14, 12, 12, 10, 10,
     10, 10, 0,  20, 8,  8,  10, 20, 10, 14, 12, 8,  5,  10, 0,  0,  0,
     0,  4,  5,  20, 20, 20, 5,  4,  4,  3,  4,  3,  1,  3},
    {10, 8,  6,  8,  0,  14, 14, 12, 12, 12, 14, 12, 12, 14, 14, 14, 8,
     8,  10, 10, 0,  10, 0,  0,  6,  12, 0,  0,  0,  8,  0,  20, 0,  4,
     20, 0,  14, 12, 12, 12, 8,  14, 12, 14, 8,  8,  14, 14, 6,  0,  6,
     0,  8,  5,  5,  8,  8,  10, 14, 14, 8,  20, 14, 14, 12, 12, 10, 10,
     10, 10, 0,  20, 8,  8,  10, 20, 10, 14, 12, 8,  5,  10, 0,  0,  0,
     0,  4,  5,  20, 20, 20, 5,  4,  4,  3,  2,  4,  3,  1},
};

MolHistogram::MolHistogram(const ROMol &mol, const double *dmat,
                           bool cleanupDmat)
    : d_h(boost::extents[mol.getNumHeavyAtoms()][O3_MAX_H_BINS]) {
  PRECONDITION(dmat, "empty distance matrix");
  unsigned int nAtoms = mol.getNumAtoms();
  unsigned int y = 0;
  for (unsigned int i = 0; i < nAtoms; ++i) {
    // skip hydrogens
    if ((mol.getAtomWithIdx(i))->getAtomicNum() == 1) {
      continue;
    }
    // initialize h row(y) to all zeros
    for (unsigned int j = 0; j < O3_MAX_H_BINS; ++j) {
      d_h[y][j] = 0;
    }
    for (unsigned int j = 0; j < nAtoms; ++j) {
      auto dist = static_cast<unsigned int>(floor(dmat[i * nAtoms + j]));
      if (dist < O3_MAX_H_BINS) {
        ++d_h[y][dist];
      }
    }
    ++y;
  }
  if (cleanupDmat) {
    delete[] dmat;
  }
}

int o3aMMFFCostFunc(const unsigned int prbIdx, const unsigned int refIdx,
                    double hSum, void *data) {
  std::uint8_t mmffSim =
      mmffSimMatrix[(static_cast<MMFF::MMFFMolProperties *>(
                         (static_cast<O3AFuncData *>(data))->refProp))
                        ->getMMFFAtomType(refIdx) -
                    1][(static_cast<MMFF::MMFFMolProperties *>(
                            (static_cast<O3AFuncData *>(data))->prbProp))
                           ->getMMFFAtomType(prbIdx) -
                       1];

  return std::lround(
      (static_cast<double>(((O3AFuncData *)data)->coeff) * O3_CHARGE_WEIGHT *
           fabs((static_cast<MMFF::MMFFMolProperties *>(
                     (static_cast<O3AFuncData *>(data))->refProp))
                    ->getMMFFPartialCharge(refIdx) -
                (static_cast<MMFF::MMFFMolProperties *>(
                     (static_cast<O3AFuncData *>(data))->prbProp))
                    ->getMMFFPartialCharge(prbIdx)) +
       (((O3AFuncData *)data)->useMMFFSim
            ? static_cast<double>(O3_MAX_WEIGHT_COEFF -
                                  ((O3AFuncData *)data)->coeff) *
                  static_cast<double>(mmffSim)
            : 0.0) +
       hSum) *
      1.0e03);
}

int o3aCrippenCostFunc(const unsigned int prbIdx, const unsigned int refIdx,
                       double hSum, void *data) {
  return std::lround(
      (static_cast<double>((static_cast<O3AFuncData *>(data))->coeff) *
           O3_CHARGE_WEIGHT *
           fabs((*(static_cast<std::vector<double> *>(
                    (static_cast<O3AFuncData *>(data))->refProp)))[refIdx] -
                (*(static_cast<std::vector<double> *>(
                    (static_cast<O3AFuncData *>(data))->prbProp)))[prbIdx]) +
       hSum) *
      1.0e03);
}

void LAP::computeCostMatrix(const ROMol &prbMol, const MolHistogram &prbHist,
                            const ROMol &refMol, const MolHistogram &refHist,
                            O3AConstraintVect *o3aConstraintVect,
                            int (*costFunc)(const unsigned int,
                                            const unsigned int, double, void *),
                            void *data, const unsigned int n_bins) {
  unsigned int i;
  unsigned int j;
  unsigned int k;
  unsigned int x;
  unsigned int y;
  unsigned int largestNHeavyAtoms =
      std::max(refMol.getNumHeavyAtoms(), prbMol.getNumHeavyAtoms());
  unsigned int mapIdx = 0;
  double hSum = 0.0;

  // ref is on rows, prb is on columns
  for (i = 0; i < largestNHeavyAtoms; ++i) {
    for (j = 0; j < largestNHeavyAtoms; ++j) {
      d_cost[i][j] = O3_DUMMY_COST;
    }
  }
  for (i = 0, y = 0; i < refMol.getNumAtoms(); ++i) {
    if (refMol[i]->getAtomicNum() == 1) {
      continue;
    }
    for (j = 0, x = 0; j < prbMol.getNumAtoms(); ++j) {
      if (prbMol[j]->getAtomicNum() == 1) {
        continue;
      }
      // if this pair is constrained, make sure that it is
      // associated with a low cost so it is picked up
      // in the minimum cost path
      // first pair element is prb, second is ref
      if (o3aConstraintVect && (mapIdx < o3aConstraintVect->size()) &&
          (j == (*o3aConstraintVect)[mapIdx]->getPrbIdx()) &&
          (i == (*o3aConstraintVect)[mapIdx]->getRefIdx())) {
        d_cost[y][x] = O3_LARGE_NEGATIVE_WEIGHT;
        ++mapIdx;
      } else {
        for (k = 0, hSum = 0.0; k < n_bins; ++k) {
          int rhyk = refHist.get(y, k);
          int phxk = prbHist.get(x, k);
          if ((!rhyk) && (!phxk)) {
            continue;
          }
          hSum += square(rhyk - phxk) / (rhyk + phxk);
        }
        d_cost[y][x] = (*costFunc)(j, i, hSum, data);
      }
      ++x;
    }
    ++y;
  }
}

//----------------------------------------------------
// Taken from Jonker, R.; Volgenant, A.
// A Shortest Augmenting Path Algorithm for Dense
// and Sparse Linear Assignment Problems
// Computing 1987, 38, 325-340
//----------------------------------------------------
void LAP::computeMinCostPath(const int dim) {
  // input:
  // dim    - problem size
  // cost   - cost matrix

  // output:
  // rowsol - column assigned to row in solution
  // colsol - row assigned to column in solution
  // v      - dual variables, column reduction numbers

  int loopCnt;
  int unassignedFound;
  // row vars
  int i;
  int imin;
  int numFree = 0;
  int prvNumFree;
  int f;
  int i0;
  int k;
  int freeRow;
  // col vars
  int j;
  int j1;
  int j2 = 0;
  int endOfPath;
  int last = 0;
  int low;
  int up;
  // cost vars
  int min = 0;
  int h;
  int uMin;
  int uSubMin;
  int v2;

  // init how many times a row will be assigned in the column reduction
  for (int n = 0; n < dim; ++n) {
    d_matches[n] = 0;
  }
  // COLUMN REDUCTION
  // reverse order gives better results
  for (j = dim - 1; j >= 0; --j) {
    // find minimum cost over rows
    min = d_cost[0][j];
    imin = 0;
    for (i = 1; i < dim; ++i) {
      if (d_cost[i][j] < min) {
        min = d_cost[i][j];
        imin = i;
      }
    }
    d_v[j] = min;

    if (++(d_matches[imin]) == 1) {
      // init assignment if minimum row assigned for first time
      d_rowSol[imin] = j;
      d_colSol[j] = imin;
    } else {
      // row already assigned, column not assigned
      d_colSol[j] = -1;
    }
  }
  // REDUCTION TRANSFER
  for (i = 0; i < dim; ++i) {
    if (!(d_matches[i])) {
      // fill list of unassigned 'd_free' rows
      d_free[numFree++] = i;
    } else {
      if (d_matches[i] == 1) {
        // transfer reduction from rows that are assigned once
        j1 = d_rowSol[i];
        min = O3_DUMMY_COST;
        for (j = 0; j < dim; ++j) {
          if ((j != j1) && (d_cost[i][j] - d_v[j] < min)) {
            min = d_cost[i][j] - d_v[j];
          }
        }
        d_v[j1] -= min;
      }
    }
  }
  // AUGMENTING ROW REDUCTION
  // do-loop to be done twice
  for (loopCnt = 0; loopCnt < 2; ++loopCnt) {
    // scan all d_free rows
    // in some cases, a d_free row may be replaced
    // with another one to be scanned next
    k = 0;
    prvNumFree = numFree;
    numFree = 0;
    // start list of rows still d_free after augmenting row reduction
    while (k < prvNumFree) {
      i = d_free[k];
      ++k;
      // find minimum and second minimum reduced cost over columns
      uMin = d_cost[i][0] - d_v[0];
      j1 = 0;
      uSubMin = O3_DUMMY_COST;
      for (j = 1; j < dim; ++j) {
        h = d_cost[i][j] - d_v[j];
        if (h < uSubMin) {
          if (h >= uMin) {
            uSubMin = h;
            j2 = j;
          } else {
            uSubMin = uMin;
            uMin = h;
            j2 = j1;
            j1 = j;
          }
        }
      }
      i0 = d_colSol[j1];
      if (uMin < uSubMin) {
        // change the reduction of the minimum column to increase
        // the minimum reduced cost in the row to the subminimum
        d_v[j1] -= (uSubMin - uMin);
      } else {
        // minimum and subminimum equal
        if (i0 >= 0) {
          // minimum column j1 is assigned
          // swap columns j1 and j2, as j2 may be unassigned
          j1 = j2;
          i0 = d_colSol[j2];
        }
      }
      // (re-)assign i to j1, possibly de-assigning an i0
      d_rowSol[i] = j1;
      d_colSol[j1] = i;

      if (i0 >= 0) {
        // minimum column j1 assigned earlier
        if (uMin < uSubMin) {
          // put in current k, and go back to that k
          // continue augmenting path i - j1 with i0
          d_free[--k] = i0;
        } else {
          // no further augmenting reduction possible
          // store i0 in list of d_free rows for next phase
          d_free[numFree++] = i0;
        }
      }
    }
  }
  // AUGMENT SOLUTION for each d_free row
  for (f = 0; f < numFree; ++f) {
    // start row of augmenting path
    freeRow = d_free[f];
    // Dijkstra shortest path algorithm
    // runs until unassigned column added to shortest path tree
    for (j = 0; j < dim; ++j) {
      d_d[j] = d_cost[freeRow][j] - d_v[j];
      d_pred[j] = freeRow;
      // init column list
      d_colList[j] = j;
    }
    // columns in 0..low-1 are ready, now none
    low = 0;
    // columns in low..up-1 are to be scanned for current minimum, now none
    up = 0;
    // columns in up..dim-1 are to be considered later to find new minimum,
    // at this stage the list simply contains all columns
    unassignedFound = 0;
    while (!unassignedFound) {
      if (up == low) {
        // no more columns to be scanned for current minimum
        last = low - 1;
        // scan columns for up..dim-1 to find all indices for which new minimum
        // occurs
        // store these indices between low..up-1 (increasing up)
        min = d_d[d_colList[up++]];
        for (k = up; k < dim; ++k) {
          j = d_colList[k];
          h = d_d[j];
          if (h <= min) {
            if (h < min) {
              // new minimum
              // restart list at index low
              up = low;
              min = h;
            }
            // new index with same minimum, put on index up, and extend list
            d_colList[k] = d_colList[up];
            d_colList[up++] = j;
          }
        }
        // check if any of the minimum columns happens to be unassigned
        // if so, we have an augmenting path right away
        for (k = low; k < up; ++k) {
          if (d_colSol[d_colList[k]] < 0) {
            endOfPath = d_colList[k];
            unassignedFound = 1;
            break;
          }
        }
      }
      if (!unassignedFound) {
        // update 'distances' between freeRow and
        // all unscanned columns, via next scanned column
        j1 = d_colList[low];
        ++low;
        i = d_colSol[j1];
        h = d_cost[i][j1] - d_v[j1] - min;
        for (k = up; k < dim; ++k) {
          j = d_colList[k];
          v2 = d_cost[i][j] - d_v[j] - h;
          if (v2 < d_d[j]) {
            d_pred[j] = i;
            if (v2 == min) {
              // new column found at same minimum value
              if (d_colSol[j] < 0) {
                // if unassigned, shortest augmenting path is complete
                endOfPath = j;
                unassignedFound = 1;
                break;
              } else {
                // else add to list to be scanned right away
                d_colList[k] = d_colList[up];
                d_colList[up++] = j;
              }
            }
            d_d[j] = v2;
          }
        }
      }
    }
    // update column prices
    for (k = 0; k <= last; ++k) {
      j1 = d_colList[k];
      d_v[j1] += (d_d[j1] - min);
    }
    // reset row and column assignments along the alternating path
    do {
      i = d_pred[endOfPath];
      d_colSol[endOfPath] = i;
      j1 = endOfPath;
      endOfPath = d_rowSol[i];
      d_rowSol[i] = j1;
    } while (i != freeRow);
  }
}

void SDM::fillFromLAP(LAP &lap) {
  unsigned int i;
  unsigned int j;
  unsigned int k;
  unsigned int n;
  unsigned int n_atoms;
  unsigned int n_equiv;
  const RDGeom::POINT3D_VECT &refPos = d_refConf->getPositions();
  const RDGeom::POINT3D_VECT &prbPos = d_prbConf->getPositions();
  const ROMol *mol[2] = {&(d_refConf->getOwningMol()),
                         &(d_prbConf->getOwningMol())};

  // filter out atom equivalences whose cost is > O3_DUMMY_COST
  for (i = 0, n_equiv = 0; i < mol[0]->getNumHeavyAtoms(); ++i) {
    if (lap.getCost(i, lap.getRowSol(i)) >= O3_DUMMY_COST) {
      continue;
    }
    auto *sdmElement = new SDMElement;
    sdmElement->idx[0] = i;
    sdmElement->idx[1] = lap.getRowSol(i);
    sdmElement->cost = lap.getCost(i, lap.getRowSol(i));
    d_SDMPtrVect.push_back(boost::shared_ptr<SDMElement>(sdmElement));
    ++n_equiv;
  }
  boost::multi_array<double, 2> diff(boost::extents[n_equiv][n_equiv]);
  // find out correspondences between heavy atom indexes
  // in the original molecule and heavy atoms in the sdm matrix
  // (the sdm matrix is rebuilt using original molecule
  // heavy atom indexes)
  for (n = 0; n < 2; ++n) {
    for (i = 0; i < n_equiv; ++i) {
      j = 0;
      n_atoms = 0;
      k = d_SDMPtrVect[i]->idx[n];
      while ((n_atoms < mol[n]->getNumAtoms()) && (j <= k)) {
        if (mol[n]->getAtomWithIdx(n_atoms)->getAtomicNum() > 1) {
          ++j;
          d_SDMPtrVect[i]->idx[n] = n_atoms;
        }
        ++n_atoms;
      }
    }
  }
  // loop over n_equiv rows
  for (i = 0; i < n_equiv; ++i) {
    d_SDMPtrVect[i]->o3aConstraint = nullptr;
    if (d_SDMPtrVect[i]->cost < 0) {
      // this is one of the constrained atom pairs, so assign it
      // a pointer to the relevant O3AConstraint object
      if (d_o3aConstraintVect) {
        for (j = 0; j < (*d_o3aConstraintVect).size(); ++j) {
          if (((*d_o3aConstraintVect)[j]->getPrbIdx() ==
               d_SDMPtrVect[i]->idx[1]) &&
              ((*d_o3aConstraintVect)[j]->getRefIdx() ==
               d_SDMPtrVect[i]->idx[0])) {
            d_SDMPtrVect[i]->o3aConstraint = (*d_o3aConstraintVect)[j];
            break;
          }
        }
      }
    }
    // initialize to 0 the i-th row of the diff matrix
    for (j = 0; j < n_equiv; ++j) {
      diff[i][j] = 0.0;
    }
    for (j = 0; j < n_equiv; ++j) {
      // if i == j then the distance will be 0,
      // no need to spend time computing it
      if (i == j) {
        continue;
      }
      // - the euclidean distance between atoms i,j is computed for ref_conf
      // - the euclidean distance between the corresponding atoms is computed
      // for moved_conf
      // - the absolute value of the difference between the two distances is
      // computed
      //   and placed in diff[i][j]
      diff[i][j] = fabs(
          (refPos[d_SDMPtrVect[i]->idx[0]] - refPos[d_SDMPtrVect[j]->idx[0]])
              .length() -
          (prbPos[d_SDMPtrVect[i]->idx[1]] - prbPos[d_SDMPtrVect[j]->idx[1]])
              .length());
    }
  }
  for (i = 0; i < n_equiv; ++i) {
    for (j = 0, d_SDMPtrVect[i]->score = 0; j < n_equiv; ++j) {
      if ((diff[i][j] > O3_THRESHOLD_DIFF_DISTANCE) &&
          (d_SDMPtrVect[i]->cost >= 0)) {
        ++(d_SDMPtrVect[i]->score);
      }
    }
  }
  // sort the SDM matrix by increasing scores
  std::sort(d_SDMPtrVect.begin(), d_SDMPtrVect.end(), this->compareSDMScore);
}

void SDM::fillFromDist(double threshold,
                       const boost::dynamic_bitset<> &refHvyAtoms,
                       const boost::dynamic_bitset<> &prbHvyAtoms) {
  unsigned int n = 0;
  unsigned int pairs = 0;
  const RDGeom::POINT3D_VECT &refPos = d_refConf->getPositions();
  const RDGeom::POINT3D_VECT &prbPos = d_prbConf->getPositions();
  unsigned int mapIdx = 0;

  d_SDMPtrVect.clear();
  double sqThreshold = square(threshold);
  unsigned int refNAtoms = d_refConf->getNumAtoms();
  unsigned int prbNAtoms = d_prbConf->getNumAtoms();
  unsigned int largestNAtoms = std::max(prbNAtoms, refNAtoms);
  boost::dynamic_bitset<> refUsed(largestNAtoms);
  boost::dynamic_bitset<> prbUsed(largestNAtoms);
  // loop over ref atoms
  for (unsigned int i = 0; i < refNAtoms; ++i) {
    if (!refHvyAtoms[i]) {
      continue;
    }
    // loop over prb atoms
    for (unsigned int j = 0; j < prbNAtoms; ++j) {
      if (!prbHvyAtoms[j]) {
        continue;
      }
      double sqDist = (refPos[i] - prbPos[j]).lengthSq();
      // if the distance between these two atoms is lower
      // than threshold, then include this pair in the SDM matrix
      bool isConstraint =
          (d_o3aConstraintVect && (mapIdx < (*d_o3aConstraintVect).size()) &&
           (j == (*d_o3aConstraintVect)[mapIdx]->getPrbIdx()) &&
           (i == (*d_o3aConstraintVect)[mapIdx]->getRefIdx()));
      if ((sqDist < sqThreshold) || isConstraint) {
        auto *sdmElement = new SDMElement();
        sdmElement->idx[0] = i;
        sdmElement->idx[1] = j;
        sdmElement->sqDist = sqDist;
        sdmElement->o3aConstraint = nullptr;
        if (isConstraint) {
          sdmElement->o3aConstraint = (*d_o3aConstraintVect)[mapIdx];
          // in case there are duplicate constraints referring to the same
          // atom pair, apply only the one with the highest weight
          while ((mapIdx < (*d_o3aConstraintVect).size()) &&
                 (j == (*d_o3aConstraintVect)[mapIdx]->getPrbIdx()) &&
                 (i == (*d_o3aConstraintVect)[mapIdx]->getRefIdx())) {
            ++mapIdx;
          }
        }
        d_SDMPtrVect.push_back(boost::shared_ptr<SDMElement>(sdmElement));
        ++n;
      }
    }
  }
  // sort the SDM matrix by decreasing constraint weights and,
  // as a second criterion, increasing distances
  std::sort(d_SDMPtrVect.begin(), d_SDMPtrVect.end(), this->compareSDMDist);
  // increase the number of pairs which will be used
  // by rmsAlgorithm until an atom which has been
  // included in a previous pair is found
  for (unsigned int i = 0; (i < n) && (!(refUsed[d_SDMPtrVect[i]->idx[0]])) &&
                           (!(prbUsed[d_SDMPtrVect[i]->idx[1]]));
       ++i) {
    ++pairs;
    refUsed[d_SDMPtrVect[i]->idx[0]] = 1;
    prbUsed[d_SDMPtrVect[i]->idx[1]] = 1;
  }
  d_SDMPtrVect.resize(pairs);
}

double o3aMMFFWeightFunc(const unsigned int prbIdx, const unsigned int refIdx,
                         void *data) {
  std::uint8_t mmffSim =
      mmffSimMatrix[(static_cast<MMFF::MMFFMolProperties *>(
                         (static_cast<O3AFuncData *>(data))->refProp))
                        ->getMMFFAtomType(refIdx) -
                    1][(static_cast<MMFF::MMFFMolProperties *>(
                            (static_cast<O3AFuncData *>(data))->prbProp))
                           ->getMMFFAtomType(prbIdx) -
                       1];

  return static_cast<double>(O3_MAX_WEIGHT_COEFF -
                             (static_cast<O3AFuncData *>(data))->weight) *
             ((1.0 + O3_CHARGE_COEFF *
                         fabs((static_cast<MMFF::MMFFMolProperties *>(
                                   (static_cast<O3AFuncData *>(data))->refProp))
                                  ->getMMFFPartialCharge(refIdx) +
                              (static_cast<MMFF::MMFFMolProperties *>(
                                   (static_cast<O3AFuncData *>(data))->prbProp))
                                  ->getMMFFPartialCharge(prbIdx))) /
              (1.0 + fabs((static_cast<MMFF::MMFFMolProperties *>(
                               (static_cast<O3AFuncData *>(data))->refProp))
                              ->getMMFFPartialCharge(refIdx) -
                          (static_cast<MMFF::MMFFMolProperties *>(
                               (static_cast<O3AFuncData *>(data))->prbProp))
                              ->getMMFFPartialCharge(prbIdx)))) +
         (double)((static_cast<O3AFuncData *>(data))->weight) /
             static_cast<double>(mmffSim);
}

double o3aCrippenWeightFunc(const unsigned int prbIdx,
                            const unsigned int refIdx, void *data) {
  return (
      0.01 +
      fabs((*(static_cast<std::vector<double> *>(
               (static_cast<O3AFuncData *>(data))->refProp)))[refIdx] +
           (*(static_cast<std::vector<double> *>(
               (static_cast<O3AFuncData *>(data))->prbProp)))[prbIdx]) /
          (1.0 +
           fabs((*(static_cast<std::vector<double> *>(
                    (static_cast<O3AFuncData *>(data))->refProp)))[refIdx] -
                (*(static_cast<std::vector<double> *>(
                    (static_cast<O3AFuncData *>(data))->prbProp)))[prbIdx])));
}

void SDM::prepareMatchWeightsVect(
    RDKit::MatchVectType &matchVect, RDNumeric::DoubleVector &weights,
    double (*weightFunc)(const unsigned int, const unsigned int, void *),
    void *data) {
  PRECONDITION(matchVect.size() == weights.size(),
               "matchVect/weights size mismatch");
  double min = MAX_DOUBLE;
  for (unsigned int i = 0; i < matchVect.size(); ++i) {
    // first pair element is prb, second is ref
    matchVect[i].first = d_SDMPtrVect[i]->idx[1];
    matchVect[i].second = d_SDMPtrVect[i]->idx[0];
    weights[i] = (weightFunc ? weightFunc(d_SDMPtrVect[i]->idx[1],
                                          d_SDMPtrVect[i]->idx[0], data)
                             : 1.0);
    if (d_SDMPtrVect[i]->o3aConstraint) {
      weights[i] += d_SDMPtrVect[i]->o3aConstraint->getWeight();
    }
    if ((!i) || (weights[i] < min)) {
      min = weights[i];
    }
  }
  for (unsigned int i = 0; i < weights.size(); ++i) {
    weights[i] /= min;
  }
}

double o3aMMFFScoringFunc(const unsigned int prbIdx, const unsigned int refIdx,
                          void *data) {
  const RDGeom::POINT3D_VECT &refPos =
      (static_cast<O3AFuncData *>(data))->refConf->getPositions();
  const RDGeom::POINT3D_VECT &prbPos =
      (static_cast<O3AFuncData *>(data))->prbConf->getPositions();

  double sqDist = (refPos[refIdx] - prbPos[prbIdx]).lengthSq();
  double chargeDiff = fabs((static_cast<MMFF::MMFFMolProperties *>(
                                (static_cast<O3AFuncData *>(data))->refProp))
                               ->getMMFFPartialCharge(refIdx) -
                           (static_cast<MMFF::MMFFMolProperties *>(
                                (static_cast<O3AFuncData *>(data))->prbProp))
                               ->getMMFFPartialCharge(prbIdx));
  double chargeSum = fabs((static_cast<MMFF::MMFFMolProperties *>(
                               (static_cast<O3AFuncData *>(data))->refProp))
                              ->getMMFFPartialCharge(refIdx) +
                          (static_cast<MMFF::MMFFMolProperties *>(
                               (static_cast<O3AFuncData *>(data))->prbProp))
                              ->getMMFFPartialCharge(prbIdx));

  return ((O3_SCORING_FUNCTION_ALPHA +
           (1.0 + O3_CHARGE_COEFF * chargeSum) / (1.0 + chargeDiff)) *
          exp(-O3_SCORING_FUNCTION_BETA * sqDist));
}

double o3aCrippenScoringFunc(const unsigned int prbIdx,
                             const unsigned int refIdx, void *data) {
  const RDGeom::POINT3D_VECT &refPos =
      (static_cast<O3AFuncData *>(data))->refConf->getPositions();
  const RDGeom::POINT3D_VECT &prbPos =
      (static_cast<O3AFuncData *>(data))->prbConf->getPositions();

  double sqDist = (refPos[refIdx] - prbPos[prbIdx]).lengthSq();
  double logpDiff =
      fabs((*(static_cast<std::vector<double> *>(
               (static_cast<O3AFuncData *>(data))->refProp)))[refIdx] -
           (*(static_cast<std::vector<double> *>(
               (static_cast<O3AFuncData *>(data))->prbProp)))[prbIdx]);
  double logpSum =
      fabs((*(static_cast<std::vector<double> *>(
               (static_cast<O3AFuncData *>(data))->refProp)))[refIdx] +
           (*(static_cast<std::vector<double> *>(
               (static_cast<O3AFuncData *>(data))->prbProp)))[prbIdx]);

  return ((O3_SCORING_FUNCTION_ALPHA +
           (1.0 + O3_CRIPPEN_COEFF * logpSum) / (1.0 + logpDiff)) *
          exp(-O3_SCORING_FUNCTION_BETA * sqDist));
}

double SDM::scoreAlignment(double (*scoringFunc)(const unsigned int,
                                                 const unsigned int, void *),
                           void *data) {
  double score = 0.0;

  for (auto &ptr : d_SDMPtrVect) {
    score += scoringFunc(ptr->idx[1], ptr->idx[0], data);
  }

  return score;
}

O3A::O3A(int (*costFunc)(const unsigned int, const unsigned int, double,
                         void *),
         double (*weightFunc)(const unsigned int, const unsigned int, void *),
         double (*scoringFunc)(const unsigned int, const unsigned int, void *),
         void *data, ROMol &prbMol, const ROMol &refMol, const int prbCid,
         const int refCid, const boost::dynamic_bitset<> &prbHvyAtoms,
         const boost::dynamic_bitset<> &refHvyAtoms, const bool reflect,
         const unsigned int maxIters, unsigned int options,
         O3AConstraintVect *o3aConstraintVect, ROMol *extWorkPrbMol,
         LAP *extLAP, MolHistogram *extPrbHist, MolHistogram *extRefHist)
    : d_prbMol(&prbMol),
      d_refMol(&refMol),
      d_prbCid(prbCid),
      d_refCid(refCid),
      d_reflect(reflect),
      d_maxIters(maxIters) {
  ROMol *workPrbMol = (extWorkPrbMol ? extWorkPrbMol : new ROMol(prbMol));
  Conformer &prbConf = workPrbMol->getConformer(prbCid);
  const Conformer &refConf = refMol.getConformer(refCid);
  unsigned int accuracy = options & O3_ACCURACY_MASK;
  bool local = options & O3_LOCAL_ONLY;
  unsigned int refNHeavyAtoms = refMol.getNumHeavyAtoms();
  unsigned int prbNHeavyAtoms = prbMol.getNumHeavyAtoms();
  unsigned int largestNHeavyAtoms = std::max(refNHeavyAtoms, prbNHeavyAtoms);
  unsigned int i;
  std::vector<unsigned int> pairs(4, 0);
  std::vector<double> score(3, 0.0);
  std::vector<double> pairsRMSD(2, 0.0);
  std::vector<SDM *> bestSDM((unsigned int)3, nullptr);
  SDM startSDM(&prbConf, &refConf, o3aConstraintVect);
  MolHistogram *refHist = nullptr;
  MolHistogram *prbHist = nullptr;
  LAP *lap = nullptr;
  if (local) {
    startSDM.fillFromDist(O3_SDM_THRESHOLD_START, refHvyAtoms, prbHvyAtoms);
  } else {
    refHist =
        (extRefHist ? extRefHist
                    : new MolHistogram(refMol,
                                       MolOps::get3DDistanceMat(
                                           refMol, refCid, false, false, ""),
                                       true));
    prbHist =
        (extPrbHist ? extPrbHist
                    : new MolHistogram(prbMol,
                                       MolOps::get3DDistanceMat(
                                           prbMol, prbCid, false, false, ""),
                                       true));
    lap = (extLAP ? extLAP : new LAP(largestNHeavyAtoms));
    lap->computeCostMatrix(prbMol, *prbHist, refMol, *refHist,
                           o3aConstraintVect, costFunc, data);
    lap->computeMinCostPath(largestNHeavyAtoms);
    startSDM.fillFromLAP(*lap);
  }
  for (pairs[0] = 0, score[0] = 0.0, i = 3; i < startSDM.size(); ++i) {
    RDKit::MatchVectType startMatchVect(i);
    RDNumeric::DoubleVector startWeights(i);
    startSDM.prepareMatchWeightsVect(startMatchVect, startWeights, weightFunc,
                                     data);
    alignMol(*workPrbMol, refMol, prbCid, refCid, &startMatchVect,
             &startWeights, reflect, maxIters);
    unsigned int sdmThresholdIt;
    for (sdmThresholdIt = ((accuracy < 1) ? 0 : O3_MAX_SDM_THRESHOLD_ITER - 1),
        pairs[1] = 0, score[1] = 0.0;
         sdmThresholdIt < O3_MAX_SDM_THRESHOLD_ITER; ++sdmThresholdIt) {
      pairs[2] = 0;
      bool flag = true;
      unsigned int iter = 0;
      double sdmThresholdDist = O3_SDM_THRESHOLD_START +
                                (double)sdmThresholdIt * O3_SDM_THRESHOLD_STEP;
      while (flag && (iter < O3_MAX_SDM_ITERATIONS)) {
        SDM progressSDM(&prbConf, &refConf, o3aConstraintVect);
        progressSDM.fillFromDist(sdmThresholdDist, refHvyAtoms, prbHvyAtoms);
        pairs[3] = progressSDM.size();
        if (pairs[3] < 3) {
          break;
        }
        RDKit::MatchVectType progressMatchVect(pairs[3]);
        RDNumeric::DoubleVector progressWeights(pairs[3]);
        progressSDM.prepareMatchWeightsVect(progressMatchVect, progressWeights,
                                            weightFunc, data);
        RDGeom::Transform3D trans;
        pairsRMSD[1] = getAlignmentTransform(
            *workPrbMol, refMol, trans, prbCid, refCid, &progressMatchVect,
            &progressWeights, reflect, maxIters);
        flag = ((pairs[3] > pairs[2]) ||
                ((pairs[3] == pairs[2]) &&
                 ((!iter) ||
                  ((pairsRMSD[0] - pairsRMSD[1]) > O3_RMSD_THRESHOLD))));
        if (flag) {
          pairs[2] = pairs[3];
          pairsRMSD[0] = pairsRMSD[1];
          if (bestSDM[2]) {
            delete bestSDM[2];
          }
          bestSDM[2] = new SDM();
          *(bestSDM[2]) = progressSDM;
          MolTransforms::transformConformer(prbConf, trans);
        }
        ++iter;
      }
      score[2] =
          ((pairs[2] >= 3) ? bestSDM[2]->scoreAlignment(scoringFunc, data)
                           : 0.0);
      if ((score[2] - score[1]) > O3_SCORE_THRESHOLD) {
        pairs[1] = pairs[2];
        score[1] = score[2];
        if (bestSDM[1]) {
          delete bestSDM[1];
        }
        bestSDM[1] = new SDM();
        *(bestSDM[1]) = *(bestSDM[2]);
      }
    }
    if ((score[1] - score[0]) > O3_SCORE_THRESHOLD) {
      pairs[0] = pairs[1];
      score[0] = score[1];
      if (bestSDM[0]) {
        delete bestSDM[0];
      }
      bestSDM[0] = new SDM();
      *(bestSDM[0]) = *(bestSDM[1]);
    }
  }
  if ((pairs[0] < 3) && (startSDM.size() >= 3)) {
    pairs[0] = startSDM.size();
    if (bestSDM[0]) {
      delete bestSDM[0];
    }
    bestSDM[0] = new SDM();
    *(bestSDM[0]) = startSDM;
    score[0] = bestSDM[0]->scoreAlignment(scoringFunc, data);
  }
  RDKit::MatchVectType *o3aMatchVect = new RDKit::MatchVectType(pairs[0]);
  auto *o3aWeights = new RDNumeric::DoubleVector(pairs[0]);
  d_o3aMatchVect = o3aMatchVect;
  d_o3aWeights = o3aWeights;
  if (pairs[0] >= 3) {
    bestSDM[0]->prepareMatchWeightsVect(*o3aMatchVect, *o3aWeights, weightFunc,
                                        data);
    d_o3aScore = score[0];
  } else {
    d_o3aScore = 0.0;
  }
  for (i = 0; i < 3; ++i) {
    if (bestSDM[i]) {
      delete bestSDM[i];
    }
  }
  if (!extLAP) {
    delete lap;
  }
  if (!extRefHist) {
    delete refHist;
  }
  if (!extPrbHist) {
    delete prbHist;
  }
  if (!extWorkPrbMol) {
    delete workPrbMol;
  }
}

O3A::O3A(ROMol &prbMol, const ROMol &refMol, void *prbProp, void *refProp,
         AtomTypeScheme atomTypes, const int prbCid, const int refCid,
         const bool reflect, const unsigned int maxIters, unsigned int options,
         const MatchVectType *constraintMap,
         const RDNumeric::DoubleVector *constraintWeights, LAP *extLAP,
         MolHistogram *extPrbHist, MolHistogram *extRefHist)
    : d_prbMol(&prbMol),
      d_refMol(&refMol),
      d_prbCid(prbCid),
      d_refCid(refCid),
      d_reflect(reflect),
      d_maxIters(maxIters) {
  int (*costFunc)(const unsigned int, const unsigned int, double, void *) =
      o3aMMFFCostFunc;
  double (*weightFunc)(const unsigned int, const unsigned int, void *) =
      o3aMMFFWeightFunc;
  double (*scoringFunc)(const unsigned int, const unsigned int, void *) =
      o3aMMFFScoringFunc;
  if (atomTypes == CRIPPEN) {
    costFunc = o3aCrippenCostFunc;
    weightFunc = o3aCrippenWeightFunc;
    scoringFunc = o3aCrippenScoringFunc;
  }
  O3AFuncData data;
  ROMol extWorkPrbMol(prbMol);
  data.prbConf = &(extWorkPrbMol.getConformer(prbCid));
  data.refConf = &(refMol.getConformer(refCid));
  data.prbProp = prbProp;
  data.refProp = refProp;
  int refNAtoms = rdcast<int>(refMol.getNumAtoms());
  int prbNAtoms = rdcast<int>(prbMol.getNumAtoms());
  boost::dynamic_bitset<> refHvyAtoms(refNAtoms);
  boost::dynamic_bitset<> prbHvyAtoms(prbNAtoms);

  for (int i = 0; i < refNAtoms; ++i) {
    if (refMol[i]->getAtomicNum() != 1) {
      refHvyAtoms.set(i);
    }
  }
  for (int i = 0; i < prbNAtoms; ++i) {
    if (prbMol[i]->getAtomicNum() != 1) {
      prbHvyAtoms.set(i);
    }
  }
  unsigned int refNHeavyAtoms = refMol.getNumHeavyAtoms();
  unsigned int prbNHeavyAtoms = prbMol.getNumHeavyAtoms();
  unsigned int largestNHeavyAtoms = std::max(refNHeavyAtoms, prbNHeavyAtoms);
  unsigned int accuracy = options & O3_ACCURACY_MASK;
  bool local = options & O3_LOCAL_ONLY;
  O3AConstraintVect o3aConstraintVect;
  if (constraintMap) {
    if (constraintWeights) {
      if ((*constraintMap).size() != (*constraintWeights).size()) {
        throw MolAlignException(
            "The number of weights should match the number of constraints");
      }
    }
    for (unsigned int i = 0; i < (*constraintMap).size(); ++i) {
      if (((*constraintMap)[i].first < 0) ||
          ((*constraintMap)[i].first >= prbNAtoms) ||
          ((*constraintMap)[i].second < 0) ||
          ((*constraintMap)[i].second >= refNAtoms)) {
        throw MolAlignException("Constrained atom idx out of range");
      }
      if ((!prbHvyAtoms[(*constraintMap)[i].first]) ||
          (!refHvyAtoms[(*constraintMap)[i].second])) {
        throw MolAlignException("Constrained atoms must be heavy atoms");
      }
      o3aConstraintVect.append(
          (*constraintMap)[i].first, (*constraintMap)[i].second,
          constraintWeights ? (*constraintWeights)[i]
                            : O3_DEFAULT_CONSTRAINT_WEIGHT);
    }
  }
  int c;
  int l;
  std::vector<double> score(2, 0.0);
  O3A *bestO3A = nullptr;
  MolHistogram *refHist = nullptr;
  MolHistogram *prbHist = nullptr;
  LAP *lap = nullptr;
  if (!local) {
    refHist =
        (extRefHist ? extRefHist
                    : new MolHistogram(refMol,
                                       MolOps::get3DDistanceMat(
                                           refMol, refCid, false, false, ""),
                                       true));
    prbHist =
        (extPrbHist ? extPrbHist
                    : new MolHistogram(prbMol,
                                       MolOps::get3DDistanceMat(
                                           prbMol, prbCid, false, false, ""),
                                       true));
    lap = (extLAP ? extLAP : new LAP(largestNHeavyAtoms));
  }
  for (l = 0, score[0] = 0.0;
       l <= (((accuracy < 2) && (atomTypes == MMFF94)) ? 1 : 0); ++l) {
    data.useMMFFSim = (bool)l;
    for (c = 0; c <= O3_MAX_WEIGHT_COEFF;
         c += ((accuracy < 3) ? 1 : (O3_MAX_WEIGHT_COEFF + 1))) {
      data.weight = (l ? c : 0);
      data.coeff = c;
      auto *o3a = new O3A(costFunc, weightFunc, scoringFunc, &data, prbMol,
                          refMol, prbCid, refCid, prbHvyAtoms, refHvyAtoms,
                          reflect, maxIters, options, &o3aConstraintVect,
                          &extWorkPrbMol, lap, prbHist, refHist);
      score[1] = o3a->score();
      if (((score[1] - score[0]) > O3_SCORE_THRESHOLD) ||
          isDoubleZero(score[0])) {
        if (bestO3A) {
          delete bestO3A;
        }
        bestO3A = o3a;
        score[0] = score[1];
      } else {
        delete o3a;
      }
    }
  }
  auto pairs = rdcast<unsigned int>(bestO3A->matches()->size());
  RDKit::MatchVectType *bestO3AMatchVect = new RDKit::MatchVectType(pairs);
  auto *bestO3AWeights = new RDNumeric::DoubleVector(pairs);
  d_o3aMatchVect = bestO3AMatchVect;
  d_o3aWeights = bestO3AWeights;
  if (pairs >= 3) {
    for (unsigned int i = 0; i < pairs; ++i) {
      (*bestO3AMatchVect)[i].first = (*(bestO3A->matches()))[i].first;
      (*bestO3AMatchVect)[i].second = (*(bestO3A->matches()))[i].second;
      (*bestO3AWeights)[i] = (*(bestO3A->weights()))[i];
    }
    d_o3aScore = bestO3A->score();
  } else {
    d_o3aScore = 0.0;
  }
  delete bestO3A;
  if (!extLAP) {
    delete lap;
  }
  if (!extRefHist) {
    delete refHist;
  }
  if (!extPrbHist) {
    delete prbHist;
  }
}

double _rmsdMatchVect(ROMol *d_prbMol, const ROMol *d_refMol,
                      const RDGeom::POINT3D_VECT &prbPos,
                      const RDGeom::POINT3D_VECT &refPos,
                      const RDKit::MatchVectType *matchVect) {
  double rmsd = 0.0;
  if (matchVect) {
    for (const auto &i : (*matchVect)) {
      // first pair element is prb, second is ref
      rmsd += (prbPos[i.first] - refPos[i.second]).lengthSq();
    }
    rmsd = sqrt(rmsd / (*matchVect).size());
  } else {
    RDGeom::Point3D refCtd;
    RDGeom::Point3D prbCtd;
    unsigned int i;
    unsigned int nHeavy;
    for (i = 0, nHeavy = 0; i < refPos.size(); ++i) {
      if (d_refMol->getAtomWithIdx(i)->getAtomicNum() == 1) {
        continue;
      }
      refCtd[0] += refPos[i][0];
      refCtd[1] += refPos[i][1];
      refCtd[2] += refPos[i][2];
      ++nHeavy;
    }
    refCtd[0] /= (double)nHeavy;
    refCtd[1] /= (double)nHeavy;
    refCtd[2] /= (double)nHeavy;
    for (i = 0, nHeavy = 0; i < prbPos.size(); ++i) {
      if (d_prbMol->getAtomWithIdx(i)->getAtomicNum() == 1) {
        continue;
      }
      prbCtd[0] += prbPos[i][0];
      prbCtd[1] += prbPos[i][1];
      prbCtd[2] += prbPos[i][2];
      ++nHeavy;
    }
    prbCtd[0] /= (double)nHeavy;
    prbCtd[1] /= (double)nHeavy;
    prbCtd[2] /= (double)nHeavy;
    rmsd = (refCtd - prbCtd).length();
  }

  return rmsd;
};

double O3A::align() {
  double rmsd = 0.0;
  const RDKit::MatchVectType *matchVectPtr = nullptr;

  if (d_o3aMatchVect && (d_o3aMatchVect->size() >= 3)) {
    alignMol(*d_prbMol, *d_refMol, d_prbCid, d_refCid, d_o3aMatchVect,
             d_o3aWeights, d_reflect, d_maxIters);
    matchVectPtr = d_o3aMatchVect;
  }
  rmsd = _rmsdMatchVect(
      d_prbMol, d_refMol, d_prbMol->getConformer(d_prbCid).getPositions(),
      d_refMol->getConformer(d_refCid).getPositions(), matchVectPtr);

  return rmsd;
}

double O3A::trans(RDGeom::Transform3D &trans) {
  double rmsd = 0.0;
  Conformer transConf(d_prbMol->getConformer(d_prbCid));
  if (d_o3aMatchVect && (d_o3aMatchVect->size() >= 3)) {
    getAlignmentTransform(*d_prbMol, *d_refMol, trans, d_prbCid, d_refCid,
                          d_o3aMatchVect, d_o3aWeights, d_reflect, d_maxIters);
    MolTransforms::transformConformer(transConf, trans);
  }
  rmsd = _rmsdMatchVect(d_prbMol, d_refMol, transConf.getPositions(),
                        d_refMol->getConformer(d_refCid).getPositions(),
                        d_o3aMatchVect);

  return rmsd;
};

void randomTransform(ROMol &mol, const int cid, const int seed) {
  if (seed > 0) {
    rng_type &generator = getRandomGenerator();
    generator.seed(seed);
  }
  Conformer &conf = mol.getConformer(cid);
  RDGeom::POINT3D_VECT &pos = conf.getPositions();
  double maxCoord[3];
  double minCoord[3];
  RDGeom::Point3D rndTrans;
  double rndRot[3];
  for (unsigned int j = 0; j < 3; ++j) {
    for (unsigned int i = 0; i < pos.size(); ++i) {
      if ((!i) || (pos[i][j] > maxCoord[j])) {
        maxCoord[j] = pos[i][j];
      }
      if ((!i) || (pos[i][j] < minCoord[j])) {
        minCoord[j] = pos[i][j];
      }
    }
    rndTrans[j] = (maxCoord[j] - minCoord[j]) * O3_RANDOM_TRANS_COEFF *
                  (getRandomVal() - 0.5);
    rndRot[j] = getRandomVal() * 2.0 * M_PI;
  }
  RDGeom::Point3D ctd = MolTransforms::computeCentroid(conf, false);
  RDGeom::Transform3D transToOrigin;
  RDGeom::Transform3D xRot;
  RDGeom::Transform3D yRot;
  RDGeom::Transform3D zRotAndTrans;
  transToOrigin.SetTranslation(-ctd);
  xRot.SetRotation(rndRot[0], RDGeom::X_Axis);
  yRot.SetRotation(rndRot[1], RDGeom::Y_Axis);
  zRotAndTrans.SetRotation(rndRot[2], RDGeom::Z_Axis);
  zRotAndTrans.SetTranslation(ctd + rndTrans);
  MolTransforms::transformConformer(conf,
                                    zRotAndTrans * yRot * xRot * transToOrigin);
}

#ifdef RDK_BUILD_THREADSAFE_SSS
namespace detail {
typedef struct {
  O3A::AtomTypeScheme atomTypes;
  int refCid;
  bool reflect;
  unsigned int maxIters;
  unsigned int options;
  MatchVectType const *constraintMap;
  RDNumeric::DoubleVector const *constraintWeights;
} O3AHelperArgs_;
void O3AHelper_(ROMol *prbMol, const ROMol *refMol, void *prbProp,
                void *refProp, std::vector<boost::shared_ptr<O3A>> *res,
                unsigned int threadIdx, int numThreads,
                const O3AHelperArgs_ *args) {
  unsigned int i = 0;
  for (ROMol::ConstConformerIterator cit = prbMol->beginConformers();
       cit != prbMol->endConformers(); ++cit, ++i) {
    if (i % numThreads != threadIdx) {
      continue;
    }

    auto *lres =
        new O3A(*prbMol, *refMol, prbProp, refProp, args->atomTypes,
                (*cit)->getId(), args->refCid, args->reflect, args->maxIters,
                args->options, args->constraintMap, args->constraintWeights);
    (*res)[i].reset(lres);
  }
}
}  // namespace detail
#endif

void getO3AForProbeConfs(ROMol &prbMol, const ROMol &refMol, void *prbProp,
                         void *refProp,
                         std::vector<boost::shared_ptr<O3A>> &res,
                         int numThreads, O3A::AtomTypeScheme atomTypes,
                         const int refCid, const bool reflect,
                         const unsigned int maxIters, unsigned int options,
                         const MatchVectType *constraintMap,
                         const RDNumeric::DoubleVector *constraintWeights) {
  numThreads = getNumThreadsToUse(numThreads);
  res.resize(prbMol.getNumConformers());
  if (numThreads == 1) {
    unsigned int i = 0;
    for (ROMol::ConstConformerIterator cit = prbMol.beginConformers();
         cit != prbMol.endConformers(); ++cit, ++i) {
      auto *lres = new O3A(prbMol, refMol, prbProp, refProp, atomTypes,
                           (*cit)->getId(), refCid, reflect, maxIters, options,
                           constraintMap, constraintWeights);
      res[i].reset(lres);
    }
  }
#ifdef RDK_BUILD_THREADSAFE_SSS
  else {
    std::vector<std::future<void>> tg;
    detail::O3AHelperArgs_ args = {atomTypes,        refCid,  reflect,
                                   maxIters,         options, constraintMap,
                                   constraintWeights};
    for (int ti = 0; ti < numThreads; ++ti) {
      tg.emplace_back(std::async(std::launch::async, detail::O3AHelper_,
                                 &prbMol, &refMol, prbProp, refProp, &res, ti,
                                 numThreads, &args));
    }
    for (auto &fut : tg) {
      fut.get();
    }
  }
#endif
}

}  // end of namespace MolAlign
}  // end of namespace RDKit
